Как создать элементарный поток AAC ADTS с помощью Android MediaCodec
Что я пытаюсь сделать: использовать Android MediaCodec для кодирования необработанных аудиосэмплов PCM в необработанный файл AAC.
У меня проблема: когда я использую FFMPEG для упаковки сгенерированного необработанного файла AAC в контейнер M4A, FFMPEG жалуется на отсутствие параметров кодека в файле.
Подробности:
Поскольку я не могу найти пример кода MediaCodec для аудиокодера, который генерирует выходной файл AAC, я попытался преобразовать видеокодер в аудиокодер. Оригинальный код здесь: source_code
Я настроил аудио кодер так:
mEncoderFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", (int)mAudioSampleRate, 2);
// redundant?
mEncoderFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
mEncoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectELD);
mEncoderFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, kSampleRates);
mEncoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates);
mEncoderFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
testEncoderWithFormat("audio/mp4a-latm", mEncoderFormat);
try {
codec.configure(
mEncoderFormat,
null /* surface */,
null /* crypto */,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IllegalStateException e) {
Log.e(TAG, "codec '" + componentName + "' failed configuration.");
return;
}
Log.d(TAG, " testEncoder configured with format = " + format);
Затем я подаю кодировщику по 10 мсек отсчетов ИКМ на кадр. Кодер берет каждый кадр, генерирует кадр битового потока, и я записываю битовый поток в FileOutputStream. Цикл продолжается до конца входного файла.
Код работает до конца. Я делаю 'adb pull', чтобы получить сгенерированный файл AAC с устройства на мой компьютер, и использую FFMPEG для его чтения. Ниже приведена команда и выдается ошибка FFMPEG:
$ ffmpeg -f aac -i BlessedNoColor_nexus7_api18.aac
ffmpeg version N-45739-g04bf2e7 Copyright (c) 2000-2012 the FFmpeg developers
built on Oct 20 2012 00:20:36 with gcc 4.7.2 (GCC)
configuration: --enable-gpl --enable-version3 --disable-pthreads --enable-runt
ime-cpudetect --enable-avisynth --enable-bzlib --enable-frei0r --enable-libass -
-enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libfreetype --enab
le-libgsm --enable-libmp3lame --enable-libnut --enable-libopenjpeg --enable-libo
pus --enable-librtmp --enable-libschroedinger --enable-libspeex --enable-libtheo
ra --enable-libutvideo --enable-libvo-aacenc --enable-libvo-amrwbenc --enable-li
bvorbis --enable-libvpx --enable-libx264 --enable-libxavs --enable-libxvid --ena
ble-zlib
libavutil 51. 76.100 / 51. 76.100
libavcodec 54. 67.100 / 54. 67.100
libavformat 54. 33.100 / 54. 33.100
libavdevice 54. 3.100 / 54. 3.100
libavfilter 3. 19.103 / 3. 19.103
libswscale 2. 1.101 / 2. 1.101
libswresample 0. 16.100 / 0. 16.100
libpostproc 52. 1.100 / 52. 1.100
[aac @ 00000000002efae0] channel element 2.0 is not allocated
[aac @ 00000000003cf520] decoding for stream 0 failed
[aac @ 00000000003cf520] Could not find codec parameters for stream 0 (Audio: aac, 0 channels, s16): unspecified sample rate
Consider increasing the value for the 'analyzeduration' and 'probesize' options
[aac @ 00000000003cf520] Estimating duration from bitrate, this may be inaccurate
BlessedNoColor_nexus7_api18.aac: could not find codec parameters
Мои вопросы:
- Я настроил кодировщик перед вызовом codec.start(). Почему в сгенерированном файле AAC отсутствуют параметры кодека?
- В исходном примере видеокодека параметры "csd-0" передаются от кодера к декодеру, но явно не записываются в файл битового потока. Нужно ли записывать их в файл AAC явно?
- Я делю входные выборки PCM на 10 мс на кадр, что не обязательно дает полный выходной пакет. Для каждого входного кадра я просто записываю то, что кодировщик выводит в файл. Это повод для беспокойства?
Любая помощь будет высоко оценена. Было бы замечательно, если бы есть пример проекта, который делает то, что я пытаюсь сделать здесь. Если мой исходный код поможет вам помочь, я опубликую его. Мне нужно сделать некоторую уборку. Спасибо!
Изменить: изменил заголовок с "Элементарный файл AAC, созданный MediaCodec с отсутствующими параметрами кодека" на "Как создать элементарный поток AAC ADTS с Android MediaCodec"
1 ответ
Наконец, я сгенерировал файлы AAC, которые можно воспроизводить как на устройстве Android, так и на хост-компьютере Windows. Я публикую свое решение здесь, надеясь, что оно может помочь другим.
Во-первых, мое предыдущее предположение, что кодировщик Android MediaCodec генерирует элементарный поток AAC, не соответствует действительности. Кодер MediaCodec генерирует необработанный поток AAC. Вот почему файлы не могут быть воспроизведены. Необработанный поток AAC необходимо преобразовать в воспроизводимый формат, такой как поток ADTS. Я изменил название этого поста, чтобы отразить мое новое понимание. Был еще один пост, который задавал аналогичный вопрос, и имел отличный ответ. Тем не менее, новичок может не обязательно понимать краткие описания там. Я не совсем понял это в первый раз, когда я прочитал этот пост.
Итак, чтобы сгенерировать поток битов AAC, который может быть воспроизведен медиаплеером, я начал с примера EncoderTest, приведенного fadden в своем 1-м комментарии, но изменил исходный код, добавив заголовок ADTS для каждого выходного кадра (блока доступа), и записать полученный поток в файл (заменены строки с 248 по 267 исходного кода следующим фрагментом кода):
if (index >= 0) {
int outBitsSize = info.size;
int outPacketSize = outBitsSize + 7; // 7 is ADTS size
ByteBuffer outBuf = codecOutputBuffers[index];
outBuf.position(info.offset);
outBuf.limit(info.offset + outBitsSize);
try {
byte[] data = new byte[outPacketSize]; //space for ADTS header included
addADTStoPacket(data, outPacketSize);
outBuf.get(data, 7, outBitsSize);
outBuf.position(info.offset);
mFileStream.write(data, 0, outPacketSize); //open FileOutputStream beforehand
} catch (IOException e) {
Log.e(TAG, "failed writing bitstream data to file");
e.printStackTrace();
}
numBytesDequeued += info.size;
outBuf.clear();
codec.releaseOutputBuffer(index, false /* render */);
Log.d(TAG, " dequeued " + outBitsSize + " bytes of output data.");
Log.d(TAG, " wrote " + outPacketSize + " bytes into output file.");
}
else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
}
else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
}
Вне цикла я определил функцию addADTStoPacket следующим образом:
/**
* Add ADTS header at the beginning of each and every AAC packet.
* This is needed as MediaCodec encoder generates a packet of raw
* AAC data.
*
* Note the packetLen must count in the ADTS header itself.
**/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
// fill in ADTS data
packet[0] = (byte)0xFF;
packet[1] = (byte)0xF9;
packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
packet[4] = (byte)((packetLen&0x7FF) >> 3);
packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
packet[6] = (byte)0xFC;
}
Я также добавил код, чтобы контролировать, как прекратить генерировать поток AAC ADTS, но это зависит от приложения, поэтому я не буду здесь подробно останавливаться. Со всеми этими изменениями сгенерированные файлы AAC можно воспроизводить на устройстве Android, на моем ПК с Windows, и ffmpeg ими доволен.