Как закодировать 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
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.