如何使用ffmpeg-API将重新采样的PCM音频编码为AAC,当输入的PCM样本计数不等于1024时

7
我正在努力将音频捕获并流式传输到RTMP服务器。我在MacOS下工作(使用Xcode),因此为了捕获音频样本缓冲区,我使用AVFoundation框架。但是为了编码和流式传输,我需要使用ffmpeg-API和libfaac编码器。因此输出格式必须为AAC(以支持iOS设备上的流播放)。
我遇到了这样的问题:音频捕获设备(在我的情况下是Logitech相机)会给我带有512个LPCM样本的样本缓冲区,并且我可以从16000、24000、36000或48000 Hz中选择输入采样率。当我将这些512个样本提供给已配置适当采样率的AAC编码器时,我听到了慢动作和抽搐的音频声音(似乎每个帧之后都有一小段静默)。
我发现(也许我错了),libfaac编码器只接受1024个采样的音频帧。当我将输入采样率设置为24000并在编码之前将输入采样缓冲区重新采样为48000时,我获得了1024个重新采样的样本。将这些1024个样本编码为AAC后,我可以在输出上听到适当的声音。但是我的网络摄像头为任何输入采样率在缓冲区中产生512个样本,而输出采样率必须为48000 Hz。因此,无论如何都需要进行重新采样,并且在重新采样后不会准确获得1024个样本。 是否有一种方法可以在ffmpeg-API功能范围内解决此问题? 非常感谢任何帮助。
PS:我想我可以累积重新采样的缓冲区,直到样本计数达到1024,然后对其进行编码,但这是流,因此会出现时间戳和其他输入设备的问题,此类解决方案不适用。
当前问题源于[问题]中描述的问题:如何使用CMSampleBufferRef(AVFoundation)获取的数据填充音频AVFrame(ffmpeg)?

这里有一个带有音频编解码器配置的代码(也有视频流,但视频工作正常):

    /*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;

    //AAC:
    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;
    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;

    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();

    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;


    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_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;


    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);

    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;

        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 the media file.
       ret = av_interleaved_write_frame(oc, &pkt);
       if (ret != 0) {
            fprintf(stderr, "Error while writing audio frame: %s\n",
                    av_err2str(ret));
            exit(1);
        }

}

iPhone支持多种音频格式,我只是好奇为什么你们只支持AAC格式。难道罗技相机不支持g711(ulaw),apcm,mpeg2 audio等其他格式吗?我们所知道的大多数相机至少支持g711格式,如果有一些额外的API,技术上也可以支持AMR格式。 - Michelle Cannon
AAC编解码器是我任务中所需的编解码器之一,而且与其他编解码器相比,我没有遇到过这样的问题。 - Aleksei2414904
嗨Aleksei2414904,我正在将PCM样本编码为AAC Android,遇到了相同的问题,请在找到解决方案时帮助我。谢谢。 - Mohit
5个回答

1
我也因为类似的问题而来到这里。我正在从Blackmagic Decklink SDI卡中读取720p50的音频和视频,这意味着每个视频帧有960个样本(48k / 50fps),我想将其与视频一起编码。当只向aacenc发送960个样本时,得到了非常奇怪的音频,并且它并没有真正抱怨这个事实。
开始使用AVAudioFifo(请参阅ffmpeg/doc/examples/transcode_aac.c),并不断向其中添加帧,直到我有足够的帧来满足aacenc。这将意味着我猜我会有一些样本播放太晚,因为第一个960应该有另一个值时,pts将设置为1024个样本。但是,就我所听/看到的而言,这并不是很明显。

0

我遇到了类似的问题。我在将PCM数据包编码为AAC时,PCM数据包的长度有时小于1024

如果我编码小于1024的数据包,则音频会变得。另一方面,如果我丢弃它,音频将变得swr_convert函数没有任何自动缓冲区,这是我的观察结果。

最终,我采用了一个缓冲区方案,将数据包填充到1024个缓冲区中,并且每当缓冲区满时就进行编码清理

填充缓冲区的函数如下:

// put frame data into buffer of fixed size
bool ffmpegHelper::putAudioBuffer(const AVFrame *pAvFrameIn, AVFrame **pAvFrameBuffer, AVCodecContext *dec_ctx, int frame_size, int &k0) {
  // prepare pFrameAudio
  if (!(*pAvFrameBuffer)) {
    if (!(*pAvFrameBuffer = av_frame_alloc())) {
      av_log(NULL, AV_LOG_ERROR, "Alloc frame failed\n");
      return false;
    } else {
      (*pAvFrameBuffer)->format = dec_ctx->sample_fmt;
      (*pAvFrameBuffer)->channels = dec_ctx->channels;
      (*pAvFrameBuffer)->sample_rate = dec_ctx->sample_rate;
      (*pAvFrameBuffer)->nb_samples = frame_size;
      int ret = av_frame_get_buffer(*pAvFrameBuffer, 0);
      if (ret < 0) {
        char err[500];
        av_log(NULL, AV_LOG_ERROR, "get audio buffer failed: %s\n",
          av_make_error_string(err, AV_ERROR_MAX_STRING_SIZE, ret));
        return false;
      }
      (*pAvFrameBuffer)->nb_samples = 0;
      (*pAvFrameBuffer)->pts = pAvFrameIn->pts;
    }
  }

  // copy input data to buffer
  int n_channels = pAvFrameIn->channels;
  int new_samples = min(pAvFrameIn->nb_samples - k0, frame_size - (*pAvFrameBuffer)->nb_samples);
  int k1 = (*pAvFrameBuffer)->nb_samples;

  if (pAvFrameIn->format == AV_SAMPLE_FMT_S16) {
    int16_t *d_in = (int16_t *)pAvFrameIn->data[0];
    d_in += n_channels * k0;
    int16_t *d_out = (int16_t *)(*pAvFrameBuffer)->data[0];
    d_out += n_channels * k1;

    for (int i = 0; i < new_samples; ++i) {
      for (int j = 0; j < pAvFrameIn->channels; ++j) {
        *d_out++ = *d_in++;
      }
    }
  } else {
    printf("not handled format for audio buffer\n");
    return false;
  }

  (*pAvFrameBuffer)->nb_samples += new_samples;
  k0 += new_samples;

  return true;
}

填充缓冲区并进行编码的循环如下:

// transcoding needed
int got_frame;
AVMediaType stream_type;
// decode the packet (do it your self)
decodePacket(packet, dec_ctx, &pAvFrame_, got_frame);

if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
    ret = 0;
    // break audio packet down to buffer
    if (enc_ctx->frame_size > 0) {
        int k = 0;
        while (k < pAvFrame_->nb_samples) {
            if (!putAudioBuffer(pAvFrame_, &pFrameAudio_, dec_ctx, enc_ctx->frame_size, k))
                return false;
            if (pFrameAudio_->nb_samples == enc_ctx->frame_size) {
                // the buffer is full, encode it (do it yourself)
                ret = encodeFrame(pFrameAudio_, stream_index, got_frame, false);
                if (ret < 0)
                    return false;
                pFrameAudio_->pts += enc_ctx->frame_size;
                pFrameAudio_->nb_samples = 0;
            }
        }
    } else {
        ret = encodeFrame(pAvFrame_, stream_index, got_frame, false);
    }
} else {
    // encode packet directly
    ret = encodeFrame(pAvFrame_, stream_index, got_frame, false);
}

不错的解决方案,我遇到了你的问题并使用了你的代码,但将其翻译成了 C#,我不确定的是 *d_out++ = *d_in++;。它是先将 *d_in 赋值给 *d_out,然后增加两个指针呢?还是先增加然后将 *d_in 赋值给 *d_out?在这两种情况下,有什么意义呢?我的意思是,直接将 *d_in 增加并分配给 *d_out 不是更好吗? - Expressingx

0

你需要将样本缓冲区分成大小为1024的块,我在安卓上录制mp3时已经这样做了。如需更多信息,请参考以下链接link1,links2


0
一种可能的解决方案是使用 asetnsamples 过滤器,该过滤器设置每个输出音频帧的样本数:

https://ffmpeg.org/ffmpeg-filters.html#asetnsamples

您可以将输入帧提供给过滤器,输出帧将具有所需数量的样本。过滤器中样本数的值应等于编码器AVCodecContextframe_size

0

如果有人来到这里,我也遇到了同样的问题,正如@Mohit所指出的,对于AAC每个音频帧都必须被分解成1024字节的块。

例如:

uint8_t *buffer = (uint8_t*) malloc(1024);
AVFrame *frame = av_frame_alloc();
while((fread(buffer, 1024, 1, fp)) == 1) {
    frame->data[0] = buffer;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接