步骤 1: 引入必要的头文件
引入FFmpeg和PortAudio的头文件。extern "C"
用于确保FFmpeg的C库能够在C++中正确链接。
#include <iostream>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}
#include <portaudio.h>
步骤 2: 初始化PortAudio和ffmpeg
PortAudio是一个跨平台的音频I/O库,用于处理音频流。
PaError err = Pa_Initialize();
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
// av_register_all 函数用于注册所有可用的文件格式(复用器和解复用器),协议(如 HTTP,RTSP 等)以及编解码器。调用这个函数后,FFmpeg 就可以识别和处理各种多媒体格式。
av_register_all();
// avcodec_register_all 函数用于注册所有可用的编解码器(解码器和编码器)。调用这个函数后,FFmpeg 就可以使用这些编解码器来处理各种音频和视频流。
avcodec_register_all();
步骤 3: 打开音频文件
使用FFmpeg的函数打开音频文件并读取文件信息。
AVFormatContext* formatContext = avformat_alloc_context();
if (avformat_open_input(&formatContext, filename, NULL, NULL) != 0) {
std::cerr << "Could not open audio file." << std::endl;
return nullptr;
}
if (avformat_find_stream_info(formatContext, NULL) < 0) {
std::cerr << "Could not find stream information." << std::endl;
return nullptr;
}
步骤 4: 查找音频流和解码器
在文件中查找音频流,并找到与之匹配的解码器。
int streamIndex = -1;
for (unsigned int i = 0; i < formatContext->nb_streams; ++i) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
streamIndex = i;
break;
}
}
if (streamIndex == -1) {
std::cerr << "Could not find audio stream." << std::endl;
return -1;
}
AVCodecParameters* codecParameters = formatContext->streams[streamIndex]->codecpar;
AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id);
if (!codec) {
std::cerr << "Unsupported codec." << std::endl;
return -1;
}
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {
std::cerr << "Could not copy codec context." << std::endl;
return -1;
}
if (avcodec_open2(codecContext, codec, NULL) < 0) {
std::cerr << "Could not open codec." << std::endl;
return -1;
}
步骤 5: 设置PortAudio流
设置PortAudio的音频流参数,并打开音频流。
PaStream* stream;
err = Pa_OpenDefaultStream(
&stream,
0, // no input channels
codecContext->channels,
paInt16, // sample format
codecContext->sample_rate,
256, // frames per buffer
NULL, // no callback, use blocking API
NULL // no callback user data
);
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
步骤 6: 播放音频
从音频文件中读
取音频数据并将其发送到PortAudio进行播放。
err = Pa_StartStream(stream);
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
AVPacket packet;
AVFrame* frame = av_frame_alloc();
while (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == streamIndex) {
if (avcodec_send_packet(codecContext, &packet) == 0) {
while (avcodec_receive_frame(codecContext, frame) == 0) {
Pa_WriteStream(stream, frame->data[0], frame->nb_samples);
}
}
}
av_packet_unref(&packet);
}
Pa_StopStream(stream);
Pa_CloseStream(stream);
Pa_Terminate();
完整代码
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <portaudio.h>
}
#include <iostream>
// 初始化FFmpeg库
void initializeFFmpeg() {
// av_register_all 函数用于注册所有可用的文件格式(复用器和解复用器),协议(如 HTTP,RTSP 等)以及编解码器。调用这个函数后,FFmpeg 就可以识别和处理各种多媒体格式。
av_register_all();
// avcodec_register_all 函数用于注册所有可用的编解码器(解码器和编码器)。调用这个函数后,FFmpeg 就可以使用这些编解码器来处理各种音频和视频流。
avcodec_register_all();
}
// 初始化PortAudio库
void initializePortAudio() {
PaError err = Pa_Initialize();
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
exit(1);
}
}
// 加载音频文件
AVFormatContext* loadAudioFile(const char* filename) {
AVFormatContext* formatContext = avformat_alloc_context();// 分配并初始化一个 AVFormatContext 结构体
if (avformat_open_input(&formatContext, filename, nullptr, nullptr) != 0) {// avformat_open_input():打开音频文件
std::cerr << "Could not open audio file." << std::endl;
return nullptr;
}
if (avformat_find_stream_info(formatContext, nullptr) < 0) {// avformat_find_stream_info():获取音频文件的流信息。
std::cerr << "Could not find stream information." << std::endl;
return nullptr;
}
return formatContext;
}
// 找到音频流的索引
int findAudioStream(AVFormatContext* formatContext) {
for (unsigned int i = 0; i < formatContext->nb_streams; ++i) {// 遍历文件中的所有流,找到第一个音频流的索引。
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
return i;
}
}
return -1;
}
// 打开解码器上下文
AVCodecContext* openCodecContext(AVFormatContext* formatContext, int streamIndex) {
AVCodecParameters* codecParameters = formatContext->streams[streamIndex]->codecpar;
AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id);// avcodec_find_decoder():查找解码器。
if (!codec) {
std::cerr << "Unsupported codec." << std::endl;
return nullptr;
}
AVCodecContext* codecContext = avcodec_alloc_context3(codec);// avcodec_alloc_context3():分配解码器上下文。
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {// avcodec_parameters_to_context():将流的参数复制到解码器上下文。
std::cerr << "Could not copy codec context." << std::endl;
return nullptr;
}
if (avcodec_open2(codecContext, codec, nullptr) < 0) {// avcodec_open2():打开解码器。
std::cerr << "Could not open codec." << std::endl;
return nullptr;
}
return codecContext;
}
// 播放音频数据
int playAudio(AVCodecContext* codecContext, AVFormatContext* formatContext, int streamIndex) {
// av_packet_alloc() 和 av_frame_alloc():分配解码所需的包和帧。
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
// 打开PortAudio流
PaStream* stream;
// Pa_OpenDefaultStream():打开默认的音频流。
PaError err = Pa_OpenDefaultStream(
&stream,
0, // 输入通道数
2, // 输出通道数,立体声
paInt16, // 采样格式,16位整数
codecContext->sample_rate, // 采样率
256, // 每个缓冲区的帧数
nullptr, // 回调函数(如果使用回调方式)
nullptr // 用户数据指针(传递给回调函数)
);
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
// 启动音频流
err = Pa_StartStream(stream);
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
// 读取并解码音频数据
while (av_read_frame(formatContext, packet) >= 0) {// av_read_frame():读取一个数据包。
if (packet->stream_index == streamIndex) {
if (avcodec_send_packet(codecContext, packet) < 0) {// avcodec_send_packet():将数据包发送到解码器。
std::cerr << "Error sending packet for decoding." << std::endl;
break;
}
while (avcodec_receive_frame(codecContext, frame) >= 0) {// avcodec_receive_frame():从解码器接收解码后的帧。
// 将解码后的音频数据写入音频流进行播放
Pa_WriteStream(stream, frame->data[0], frame->nb_samples);// Pa_WriteStream():将解码后的音频数据写入音频流进行播放。
}
}
av_packet_unref(packet);// 释放packet以便重复使用
}
// 停止音频流
err = Pa_StopStream(stream);
if (err != paNoError) {
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
return -1;
}
// 关闭音频流
Pa_CloseStream(stream);
// av_frame_free() 和 av_packet_free():释放帧和包。
av_frame_free(&frame);
av_packet_free(&packet);
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <audio file>" << std::endl;
return -1;
}
initializeFFmpeg();
initializePortAudio();
AVFormatContext* formatContext = loadAudioFile(argv[1]);
if (!formatContext) {
return -1;
}
int audioStreamIndex = findAudioStream(formatContext);
if (audioStreamIndex < 0) {
std::cerr << "Could not find audio stream." << std::endl;
return -1;
}
AVCodecContext* codecContext = openCodecContext(formatContext, audioStreamIndex);
if (!codecContext) {
return -1;
}
if (playAudio(codecContext, formatContext, audioStreamIndex) < 0) {
return -1;
}
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
Pa_Terminate();
return 0;
}
关键点和难点
- 库的引入和链接:确保FFmpeg和PortAudio库已经正确安装,并配置好了开发环境。编译时需指定正确的库路径和头文件路径。
- 音频解码:使用FFmpeg解码音频文件,涉及到查找流信息、找到正确的解码器、并将音频数据解码为PCM格式。
- 音频播放:使用PortAudio处理解码后的PCM数据流,确保音频输出设备的配置正确(采样率、通道数等)。
- 错误处理:添加错误处理代码,以便在库函数调用失败时能够进行诊断和调试。