ffmpeg音频文件的读取播放代码流程

步骤 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;
}

关键点和难点

  1. 库的引入和链接:确保FFmpeg和PortAudio库已经正确安装,并配置好了开发环境。编译时需指定正确的库路径和头文件路径。
  2. 音频解码:使用FFmpeg解码音频文件,涉及到查找流信息、找到正确的解码器、并将音频数据解码为PCM格式。
  3. 音频播放:使用PortAudio处理解码后的PCM数据流,确保音频输出设备的配置正确(采样率、通道数等)。
  4. 错误处理:添加错误处理代码,以便在库函数调用失败时能够进行诊断和调试。

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值