使用FFMPEG和PortAudio进行AAC音频编码

在音频相关应用的开发中,将捕获的PCM数据编码为AAC格式并写入音频文件是一项常见的任务。本文旨在为对此感兴趣的音频软件开发者提供指导,特别是那些希望在Windows平台上使用FFMPEG库进行音频编码和解码的开发者。虽然了解AAC编码和解码的细节并非必要,但如果想要手动解码捕获的数据,则需要具备一定的知识。

本文主要面向软件开发者,特别是音频软件开发者。整个应用程序使用C语言编写,包含简单的函数调用以及PortAudio和FFMPEG库的使用。由于程序大量使用FFMPEG库的调用,因此最好具备一定的FFMPEG知识。

PortAudio简介

PortAudio是一个免费的、跨平台的、开源的音频I/O库。它允许开发者使用C或C++编写简单的音频程序,这些程序可以在多个平台上编译和运行,包括Windows、Macintosh OS X和Unix(OSS/ALSA)。PortAudio旨在促进不同平台开发者之间的音频软件交换。PortAudio软件包含许多有用的示例。

官方网站:

FFMPEG简介

FFMPEG是一个非常快速的视频和音频转换器,也可以从实时音视频源捕获。它还可以在转换过程中任意调整采样率和调整视频大小,同时使用高质量的多相位滤波器。FFMPEG可以从任意数量的输入"文件"(可以是常规文件、管道、网络流、抓取设备等)读取,通过-i选项指定,并写入任意数量的输出"文件",通过简单的输出文件名指定。命令行上任何不能解释为选项的内容都被视为输出文件名。

官方网站:

AAC音频编码数据流图

数据流图如下:

(此处应有数据流图,但因格式限制无法显示)

软件细节

有关PortAudio调用的更多细节,请参见PortAudio文档,有关AMR窄带的更多信息,请参见opencore-amr文档。

初始化PortAudio并使用以下配置打开PortAudio流:8000采样率、16位有符号PCM数据、帧大小为80样本,即每帧10毫秒的音频数据。流启动后,每10毫秒调用一次记录回调函数,传入一帧音频数据。

static PaError yakAudioStreamOpen(paTestData *yakData) { PaStreamParameters inputParameters; PaStream* stream; PaError err = paNoError; // register signal SIGINT and signal handler signal(SIGINT, signalHandler); err = Pa_Initialize(); if (err != paNoError) goto done; inputParameters.device = Pa_GetDefaultInputDevice(); if (inputParameters.device == paNoDevice) { fprintf(stderr, "Error: No default input device.\n"); goto done; } inputParameters.channelCount = NUM_CHANNELS; inputParameters.sampleFormat = PA_SAMPLE_TYPE; inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency; inputParameters.hostApiSpecificStreamInfo = NULL; err = Pa_OpenStream(&stream, &inputParameters, NULL, SAMPLE_RATE, SAMPLES_PER_FRAME, paClipOff, yakAudioRecordCallback, yakData); if (err != paNoError) goto done; yakData->recordStream = stream; return paNoError; done: Pa_Terminate(); if (err != paNoError) { fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); err = 1; } return err; }

初始化opencore-amr库,配置参数以匹配Port-audio捕获配置。

AVCodecContext* audioCodec; AVCodec *codec; avcodec_register_all(); // Set up audio encoder codec = avcodec_find_encoder(CODEC_ID_AAC); if (codec == NULL) { printf("avcodec_find_encoder: ERROR\n"); return NULL; } audioCodec = avcodec_alloc_context(); audioCodec->bit_rate = audioBitrate; audioCodec->sample_fmt = SAMPLE_FMT_S16; audioCodec->sample_rate = sampleRate; audioCodec->channels = channels; audioCodec->profile = FF_PROFILE_AAC_MAIN; audioCodec->time_base.num = 1; audioCodec->time_base.den = sampleRate; audioCodec->codec_type = AVMEDIA_TYPE_AUDIO; if (avcodec_open(audioCodec, codec) < 0) { printf("avcodec_open: ERROR\n"); return NULL; } return audioCodec;

当Port-audio记录回调被调用时,原始音频数据存储在输入缓冲区中,将缓冲区和长度传递给AMR编码器,AMR编码器返回编码数据。

static void yakAudioEncode(paTestData *yakData, uint8_t *rawData, int rawDataSize) { int frameBytes; frameBytes = yakData->c->frame_size * yakData->c->channels * sizeof(SAMPLE); while (rawDataSize >= frameBytes) { int packetSize; packetSize = avcodec_encode_audio(yakData->c, yakData->encoderOutput, yakData->allocOutputSize, (short*)rawData); yakData->encoderOutputSize += packetSize; rawData += frameBytes; rawDataSize -= frameBytes; } }

将编码后的数据写入文件。

static void EncodePaData(paTestData *yakData, uint8_t *rawData, int rawDataSize) { yakAudioEncode(yakData, rawData, rawDataSize); fwrite(yakData->encoderOutput, sizeof(uint8_t), yakData->encoderOutputSize, yakData->recFileStream); yakData->encoderOutputSize = 0; }

关闭与FFMPEG相关的所有资源,包括AvCodecContext和AvCodec库。

static PaError yakCloseAudioStream(paTestData *yakData) { return Pa_CloseStream(yakData->recordStream); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485