在音频相关应用的开发中,将捕获的PCM数据编码为AAC格式并写入音频文件是一项常见的任务。本文旨在为对此感兴趣的音频软件开发者提供指导,特别是那些希望在Windows平台上使用FFMPEG库进行音频编码和解码的开发者。虽然了解AAC编码和解码的细节并非必要,但如果想要手动解码捕获的数据,则需要具备一定的知识。
本文主要面向软件开发者,特别是音频软件开发者。整个应用程序使用C语言编写,包含简单的函数调用以及PortAudio和FFMPEG库的使用。由于程序大量使用FFMPEG库的调用,因此最好具备一定的FFMPEG知识。
PortAudio是一个免费的、跨平台的、开源的音频I/O库。它允许开发者使用C或C++编写简单的音频程序,这些程序可以在多个平台上编译和运行,包括Windows、Macintosh OS X和Unix(OSS/ALSA)。PortAudio旨在促进不同平台开发者之间的音频软件交换。PortAudio软件包含许多有用的示例。
官方网站:
FFMPEG是一个非常快速的视频和音频转换器,也可以从实时音视频源捕获。它还可以在转换过程中任意调整采样率和调整视频大小,同时使用高质量的多相位滤波器。FFMPEG可以从任意数量的输入"文件"(可以是常规文件、管道、网络流、抓取设备等)读取,通过-i选项指定,并写入任意数量的输出"文件",通过简单的输出文件名指定。命令行上任何不能解释为选项的内容都被视为输出文件名。
官方网站:
数据流图如下:
(此处应有数据流图,但因格式限制无法显示)
有关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);
}