Как закодировать PCM-аудио в AAC-формат с помощью ffmpeg-API
От: Lexx47  
Дата: 03.06.13 21:15
Оценка:
Я работаю над потоковым вещанием аудио и видео с веб камеры на rtmp-сервер. Код пишу под MacOS (в Xcode) поэтому для захвата аудио пользуюсь AVFoundation-framework'ом. Это аудио требуется на лету закодировать с помощью ffmpeg'a в AAC-формат (для совместимости с iOS-девайсами) и послать.

Камера отдает мне аудио в виде 16-бит PCM буферами по 512 семплов в каждом (причем не зависимо от выбранного на камере семлпрейта (выбрать можно 16, 24,32 и 48 кГц)). Если я беру эти данные и кодирую ААС енкодером настроенным на соответствующий семплрейт (через libfaac), то на выходе слышу сильно замедленный и прерывистый звук (как будто после каждого аудио фрейма добавляют кусок тишины).

После длительного поиска, пришел к выводу, что проблема скорее всего в том, что ААС-енкодер принимает на вход буфер аудио размером в 1024 семпла, и если он меньше — то считает что это конец файла и добивает тишиной, а если больше — валится с сообщением что размер буфера превышает размер фрейма (возможно я здесь ошибаюсь, но других объяснений пока не нашел). Для проверки я сделал так: если я выбираю входной семплрейт 24000, выходной в енкодере 48000 Гц, и делаю ресемплинг входных 512 семплов в 48000 Гц, то я получаю буфер с 1024 семплами — если закодировать его, то аудио звучит отлично и без рывков. Но это не решение (нужно чтото поуниверсальнее, и 24Кгц на входе маловато), и для другого девайса семплрейты могут быть не кратные 48000, и тогда после ресемплинга я никак не смогу получить 1024 семпла в буфере.

Вопрос — как средствами ffmpeg-библиотек подготовить буфер правильного размера для ААС енкодера, или хе как заставить ААС енкодер нормально принимать входной семплбуфер переменной длины?

Я предполагаю, что можно искусственно накапливать семплы после ресемплера, пока не наберется их 1024 и затем отдавать на енкодер, но это ведь RTMP-стрим, и начнутся проблемы с пересчетом таймштампов и длительностей, так что это неподходящее решение. Да и мне кажется такая функциональность должна быть заложена в енкодере.

Буду благодарен за совет.

Вот код инициализации аудио кодека:

/*global variables*/
    static AVFrame *aframe;
    static AVFrame *frame;
    AVOutputFormat *fmt; 
    AVFormatContext *oc; 
    AVStream *audio_st, *video_st;
Init ()
{
    AVCodec *audio_codec, *video_codec;
    int ret;

    avcodec_register_all();  
    av_register_all();
    avformat_network_init();
    avformat_alloc_output_context2(&oc, NULL, "flv", filename);
    fmt = oc->oformat;
    oc->oformat->video_codec = AV_CODEC_ID_H264;
    oc->oformat->audio_codec = AV_CODEC_ID_AAC;
    video_st = NULL;
    audio_st = NULL;
    if (fmt->video_codec != AV_CODEC_ID_NONE) 
      { //…  /*init video codec*/}
    if (fmt->audio_codec != AV_CODEC_ID_NONE) {
    audio_codec= avcodec_find_encoder(fmt->audio_codec);

    if (!(audio_codec)) {
        fprintf(stderr, "Could not find encoder for '%s'\n",
                avcodec_get_name(fmt->audio_codec));
        exit(1);
    }
    audio_st= avformat_new_stream(oc, audio_codec);
    if (!audio_st) {
        fprintf(stderr, "Could not allocate stream\n");
        exit(1);
    }
    audio_st->id = oc->nb_streams-1;

    //initializing AAC codec:
    audio_st->codec->sample_fmt  = AV_SAMPLE_FMT_S16;
    audio_st->codec->bit_rate    = 32000;
    audio_st->codec->sample_rate = 48000;
    audio_st->codec->profile=FF_PROFILE_AAC_LOW;
    audio_st->time_base = (AVRational){1, audio_st->codec->sample_rate };
    audio_st->codec->channels    = 1;
    audio_st->codec->channel_layout = AV_CH_LAYOUT_MONO;      


    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
        audio_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    if (video_st)
    {
    //   …
    /*prepare video*/
    }
    if (audio_st)
    {
    aframe = avcodec_alloc_frame();
    if (!aframe) {
        fprintf(stderr, "Could not allocate audio frame\n");
        exit(1);
    }
    AVCodecContext *c;
    int ret;

    c = audio_st->codec;


    ret = avcodec_open2(c, audio_codec, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret));
        exit(1);
    }

    //…
}


И код собственно ресемплинга и кодирования в ААС:


if (mType == kCMMediaType_Audio)
{
    CMSampleTimingInfo timing_info;
     // in sampleBuffer - rawdata from web cam (produced by AvFoundation)
    CMSampleBufferGetSampleTimingInfo(sampleBuffer, 0, &timing_info);

    double  pts=0;
    double  dts=0;
    AVCodecContext *c;
    AVPacket pkt = { 0 }; // data and size must be 0;
    int got_packet, ret;
     av_init_packet(&pkt);
    c = audio_st->codec;

      CMItemCount numSamples = CMSampleBufferGetNumSamples(sampleBuffer);

    NSUInteger channelIndex = 0;

    //obtaining description of input audio data
    CMBlockBufferRef audioBlockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    size_t audioBlockBufferOffset = (channelIndex * numSamples * sizeof(SInt16));
    size_t lengthAtOffset = 0;
    size_t totalLength = 0;
    SInt16 *samples = NULL;
    CMBlockBufferGetDataPointer(audioBlockBuffer, audioBlockBufferOffset, &lengthAtOffset, &totalLength, (char **)(&samples));

    const AudioStreamBasicDescription *audioDescription = CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));

    SwrContext *swr = swr_alloc();

    //configuring resampler
    int in_smprt = (int)audioDescription->mSampleRate;
    av_opt_set_int(swr, "in_channel_layout",  AV_CH_LAYOUT_MONO, 0);

    av_opt_set_int(swr, "out_channel_layout", audio_st->codec->channel_layout,  0);

    av_opt_set_int(swr, "in_channel_count", audioDescription->mChannelsPerFrame,  0);
    av_opt_set_int(swr, "out_channel_count", audio_st->codec->channels,  0);

    av_opt_set_int(swr, "out_channel_layout", audio_st->codec->channel_layout,  0);
    av_opt_set_int(swr, "in_sample_rate",     audioDescription->mSampleRate,0);

    av_opt_set_int(swr, "out_sample_rate",    audio_st->codec->sample_rate,0);

    av_opt_set_sample_fmt(swr, "in_sample_fmt",  AV_SAMPLE_FMT_S16, 0);

    av_opt_set_sample_fmt(swr, "out_sample_fmt", audio_st->codec->sample_fmt,  0);

    swr_init(swr);
    uint8_t **input = NULL;
    int src_linesize;
    int in_samples = (int)numSamples;
    ret = av_samples_alloc_array_and_samples(&input, &src_linesize, audioDescription->mChannelsPerFrame,
                                             in_samples, AV_SAMPLE_FMT_S16P, 0);


    *input=(uint8_t*)samples;
    uint8_t *output=NULL;

    //calculating nim of output samples
    int out_samples = av_rescale_rnd(swr_get_delay(swr, in_smprt) +in_samples, (int)audio_st->codec->sample_rate, in_smprt, AV_ROUND_UP);
    //resampling
    av_samples_alloc(&output, NULL, audio_st->codec->channels, out_samples, audio_st->codec->sample_fmt, 0);
    in_samples = (int)numSamples;
    out_samples = swr_convert(swr, &output, out_samples, (const uint8_t **)input, in_samples);


    aframe->nb_samples =(int) out_samples;

    //prepearing AVFrame for encoding
    ret = avcodec_fill_audio_frame(aframe, audio_st->codec->channels, audio_st->codec->sample_fmt,
                             (uint8_t *)output,
                             (int) out_samples *
                             av_get_bytes_per_sample(audio_st->codec->sample_fmt) *
                             audio_st->codec->channels, 1);

    aframe->channel_layout = audio_st->codec->channel_layout;
    aframe->channels=audio_st->codec->channels;
    aframe->sample_rate= audio_st->codec->sample_rate;

    if (timing_info.presentationTimeStamp.timescale!=0)
        pts=(double) timing_info.presentationTimeStamp.value/timing_info.presentationTimeStamp.timescale;

    aframe->pts=pts*audio_st->time_base.den;
    aframe->pts = av_rescale_q(aframe->pts, audio_st->time_base, audio_st->codec->time_base);
    //encoding
    ret = avcodec_encode_audio2(c, &pkt, aframe, &got_packet);

    if (ret < 0) {
        fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret));
        exit(1);
    }
    swr_free(&swr);
    if (got_packet)
    {
        pkt.stream_index = audio_st->index;
        //rescaling timestamps
        pkt.pts = av_rescale_q(pkt.pts, audio_st->codec->time_base, audio_st->time_base);
        pkt.dts = av_rescale_q(pkt.dts, audio_st->codec->time_base, audio_st->time_base);
         
        // Write the compressed frame to rtmp stream
       ret = av_interleaved_write_frame(oc, &pkt);
       if (ret != 0) {
            fprintf(stderr, "Error while writing audio frame: %s\n",
                    av_err2str(ret));
            exit(1);
        }

}
aac audio encoding ffmpeg libavcodec
Re: Как закодировать PCM-аудио в AAC-формат с помощью ffmpeg-API
От: roro  
Дата: 20.06.13 17:46
Оценка:
Здравствуйте, Lexx47, Вы писали:

L>Я работаю над потоковым вещанием аудио и видео с веб камеры на rtmp-сервер. Код пишу под MacOS (в Xcode) поэтому для захвата аудио пользуюсь AVFoundation-framework'ом. Это аудио требуется на лету закодировать с помощью ffmpeg'a в AAC-формат (для совместимости с iOS-девайсами) и послать.


L>Камера отдает мне аудио в виде 16-бит PCM буферами по 512 семплов в каждом (причем не зависимо от выбранного на камере семлпрейта (выбрать можно 16, 24,32 и 48 кГц)). Если я беру эти данные и кодирую ААС енкодером настроенным на соответствующий семплрейт (через libfaac), то на выходе слышу сильно замедленный и прерывистый звук (как будто после каждого аудио фрейма добавляют кусок тишины).


Это очень странно, проверьте правильно ли вытаскивается буфер, там либо пакеты должны чаше приходить либо размер буфера не 512.

Для установки опций ресемплинга есть удобная функция swr_alloc_set_opts

вот тут вообще нипонятно,
L> int out_samples = av_rescale_rnd(swr_get_delay(swr, in_smprt) +in_samples, (int)audio_st->codec->sample_rate, in_smprt, AV_ROUND_UP);
av_rescale_rnd — Rescales a 64-bit integer with specified rounding.
а вы из нее вычитали количество семплов.

проверьте внимательно параметры, скорее всего ошибка в какой-нибудь мелочи.
Re[2]: Как закодировать PCM-аудио в AAC-формат с помощью ffmpeg-API
От: Lexx47  
Дата: 25.06.13 10:33
Оценка:
Здравствуйте, roro,спасибо за ответ!
Вы писали:

R>вот тут вообще нипонятно,

L>> int out_samples = av_rescale_rnd(swr_get_delay(swr, in_smprt) +in_samples, (int)audio_st->codec->sample_rate, in_smprt, AV_ROUND_UP);
R>av_rescale_rnd — Rescales a 64-bit integer with specified rounding.
R> а вы из нее вычитали количество семплов.

Меня это тоже несколько встревожило, но такой вариант пересчета количества выходных семплов я обнаружил на оф. документации ffmpeg'а.

L>>Камера отдает мне аудио в виде 16-бит PCM буферами по 512 семплов в каждом (причем не зависимо от выбранного на камере семлпрейта (выбрать можно 16, 24,32 и 48 кГц)). Если я беру эти данные и кодирую ААС енкодером настроенным на соответствующий семплрейт (через libfaac), то на выходе слышу сильно замедленный и прерывистый звук (как будто после каждого аудио фрейма добавляют кусок тишины).


R>Это очень странно, проверьте правильно ли вытаскивается буфер, там либо пакеты должны чаше приходить либо размер буфера не 512.

R>проверьте внимательно параметры, скорее всего ошибка в какой-нибудь мелочи.

Проверил настройки кодека и информацию из семпл-буффера — пока ничего нового не нашел. Еще провел такой тест: попробовал кодировать всё с такими же параметрами, но в mp3 ( через libmp3lame). Тоесть поменял только инициализацию аудио кодека на:


          //oc->oformat->audio_codec = AV_CODEC_ID_AAC;
           oc->oformat->audio_codec = AV_CODEC_ID_MP3;
...

          //audio_st->codec->sample_fmt  = AV_SAMPLE_FMT_S16;
           audio_st->codec->sample_fmt  = AV_SAMPLE_FMT_S16P; 

         //audio_st->codec->sample_rate = 48000;
           audio_st->codec->sample_rate = 44100; // семплрейт поменял из за того что flv контейнер не захотел принимать mp3 c 48 kHz.


В таком случае аудио воспроизводиться нормально, причем для любого входного семплрейта. Тоесть выходит ресемплер и рамзмер буффера всетаки правильны, а проблема либо в настройках AAC енкодера, либо в самом енкодере? Я использую libfaac, может для него есть какието особенности или нужно делать дополнительную обработку?
Re[3]: Как закодировать PCM-аудио в AAC-формат с помощью ffmpeg-API
От: roro  
Дата: 26.06.13 19:53
Оценка:
Здравствуйте, Lexx47, Вы писали:

L>Здравствуйте, roro,спасибо за ответ!

L>Вы писали:

R>>вот тут вообще нипонятно,

L>>> int out_samples = av_rescale_rnd(swr_get_delay(swr, in_smprt) +in_samples, (int)audio_st->codec->sample_rate, in_smprt, AV_ROUND_UP);
R>>av_rescale_rnd — Rescales a 64-bit integer with specified rounding.
R>> а вы из нее вычитали количество семплов.

L>Меня это тоже несколько встревожило, но такой вариант пересчета количества выходных семплов я обнаружил на оф. документации ffmpeg'а.


Посчитал на калькуляторе, да правильно сходится.
Заметил что в коде swr context создается на каждом фрейме, обычно его один раз инициализируют, возможно он накапливает какие-то данные, между вызовами ресемплинга.

L>>>Камера отдает мне аудио в виде 16-бит PCM буферами по 512 семплов в каждом (причем не зависимо от выбранного на камере семлпрейта (выбрать можно 16, 24,32 и 48 кГц)). Если я беру эти данные и кодирую ААС енкодером настроенным на соответствующий семплрейт (через libfaac), то на выходе слышу сильно замедленный и прерывистый звук (как будто после каждого аудио фрейма добавляют кусок тишины).


R>>Это очень странно, проверьте правильно ли вытаскивается буфер, там либо пакеты должны чаше приходить либо размер буфера не 512.

R>>проверьте внимательно параметры, скорее всего ошибка в какой-нибудь мелочи.

L>Проверил настройки кодека и информацию из семпл-буффера — пока ничего нового не нашел. Еще провел такой тест: попробовал кодировать всё с такими же параметрами, но в mp3 ( через libmp3lame). Тоесть поменял только инициализацию аудио кодека на:



L>
L>          //oc->oformat->audio_codec = AV_CODEC_ID_AAC;
           oc->>oformat->audio_codec = AV_CODEC_ID_MP3;
L>...

L>          //audio_st->codec->sample_fmt  = AV_SAMPLE_FMT_S16;
L>           audio_st->codec->sample_fmt  = AV_SAMPLE_FMT_S16P; 

L>         //audio_st->codec->sample_rate = 48000;
L>           audio_st->codec->sample_rate = 44100; // семплрейт поменял из за того что flv контейнер не захотел принимать mp3 c 48 kHz.

L>


L>В таком случае аудио воспроизводиться нормально, причем для любого входного семплрейта. Тоесть выходит ресемплер и рамзмер буффера всетаки правильны, а проблема либо в настройках AAC енкодера, либо в самом енкодере? Я использую libfaac, может для него есть какието особенности или нужно делать дополнительную обработку?


Насчет енкодера не знаю, сталкивались что на многоканальном звуке AAC декодер иногда хрипит.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.