开发环境
- FFmpeg 3.4
- window
- Visual Studio 2022
开发过程
1. 使用VS 创建 FFmpegExamples 控制台工程
2. 在VS 里面将FFmpeg库加入依赖环境。
- 准备一个FFmpeg的x64库,我这里有一个我编译好的,可以下载使用
静态库下载地址github.com/SnailCoderG…
我在博客里面有介绍这个库的编译过程 juejin.cn/post/736067…
- 加入头文件目录
-
加入.lib库 将静态库bin目录的.lib文件加入库文件
-
指定静态库bin目录
- 拷贝静态库里bin目录里面的dll文件到运行目录
3. 创建 FileDecode.cpp FileDecode.h 文件
#include <string>
#include <iostream>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
class FileDecode
{
public:
int OpenFile(std::string filename);
int OpenAudioDecode();
int Decode();
void Close();
private:
int DecodeAudio(AVPacket* originalPacket);
private:
AVFormatContext* formatCtx = NULL;
AVCodecContext* codecCtx = NULL;
FILE* outdecodedfile;
int audioStream;
};
注意上面引入ffmpeg头文件,时候要加入extern,因为ffmpeg是c语言编写的,错误话会找不到符号
3. 编写OpenFile 方法
int FileDecode::OpenFile(std::string filename)
{
//这里用于保存后面解码后的音频pcm数据
outdecodedfile = fopen("decode.pcm", "wb");
if (!outdecodedfile) {
std::cout << "open out put file failed";
}
//打开一个音频文件,这里可以返回一个format上下位
int openInputResult = avformat_open_input(&formatCtx, filename.c_str(), NULL, NULL);
if (openInputResult != 0) {
std::cout << "open input failed" << std::endl;
return -1;
}
//根据format去解复用流信息
if (avformat_find_stream_info(formatCtx, NULL) < 0) {
std::cout << "find stram info faild" << std::endl;
return -1;
}
//打印format信息
av_dump_format(formatCtx, 0, filename.c_str(), 0);
//查找一些最优的音频流信息,返回音频流序号
audioStream = av_find_best_stream(formatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audioStream < 0) {
std::cout << "av find best stream failed" << std::endl;
return -1;
}
return 0;
}
4. 编写OpenAudioDecode 方法
int FileDecode::OpenAudioDecode()
{
//根据fomrat上下文返回音频流的编码山下文codecCtx
codecCtx = formatCtx->streams[audioStream]->codec;
//根据code上下文返回解码器
AVCodec* codec = avcodec_find_decoder(codecCtx->codec_id);
if (codec == NULL) {
std::cout << "cannot find codec id: " << codecCtx->codec_id << std::endl;
return -1;
}
// 打开解码器
AVDictionary* dict = NULL;
int codecOpenResult = avcodec_open2(codecCtx, codec, &dict);
if (codecOpenResult < 0) {
std::cout << "open decode faild" << std::endl;
return -1;
}
return 0;
}
5. 编写Decode方法
int FileDecode::Decode()
{
//创建一个AVPacket 用于放解码前数据
AVPacket avpkt;
do {
av_init_packet(&avpkt);
//读出一帧解码数据
if (av_read_frame(formatCtx, &avpkt) < 0) {
//没有读到数据,说明结束了
return 0;
}
if (avpkt.stream_index == audioStream)
{
//std::cout << "read one audio frame" << std::endl;
//送入音频解码方法
DecodeAudio(&avpkt);
av_packet_unref(&avpkt);
}
else {
//暂时不处理其他针
av_packet_unref(&avpkt);
continue;
}
} while (avpkt.data == NULL);
}
6. 编写 DecodeAudio 方法
int FileDecode::DecodeAudio(AVPacket* originalPacket)
{
//将解码数据送入解码器
int ret = avcodec_send_packet(codecCtx, originalPacket);
if (ret < 0)
{
return -1;
}
AVFrame* frame = av_frame_alloc();
//从解码器获取解码后的数据
ret = avcodec_receive_frame(codecCtx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
return -2;
}else if (ret < 0) {
std::cout << "error decoding";
return -1;
}
//返回每个采样占用几个字节
int data_size = av_get_bytes_per_sample(codecCtx->sample_fmt);
if (data_size < 0) {
/* This should not occur, checking just for paranoia */
std::cout << "Failed to calculate data size\n";
return -1;
}
//将解码后数据写入文件
for (int i = 0; i < frame->nb_samples; i++)
for (int ch = 0; ch < codecCtx->channels; ch++)
fwrite(frame->data[ch] + data_size * i, 1, data_size, outdecodedfile);
av_frame_free(&frame);
return 0;
}
7. 编写 Close 方法
void FileDecode::Close()
{
fclose(outdecodedfile);
avformat_close_input(&formatCtx);
avcodec_free_context(&codecCtx);
}
8. 主程序调用
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
#include "FileDecode.h"
int main() {
// 注册所有的编解码器、格式和协议
av_register_all();
FileDecode fileDecode;
int ret = fileDecode.OpenFile("oceans.mp4");
if (ret != 0)
{
std::cout << "OpenFile Faild";
}
ret = fileDecode.OpenAudioDecode();
ret = fileDecode.Decode();
if (ret == 0)
{
std::cout << "解码音频完成" << std::endl;
}
fileDecode.Close();
return 0;
}
准备一个视频文件放到一个目录,我的这个oceans.mp4放在了解决方案里面,运行后会生成 decode.pcm文件
9. 可以使用 Audacity 测试这个pcm数据是否正常播放
这里的采样率,编码位数根据自己实际情况输入
打开后的波形图,点击 三角形开始按钮就可以播放
9. 总结
程序相对使用基础,但是avformat_open_input 在开开一些音频后,并不是一定能返回正确的媒体信息。在实际的使用场景中,很多是通过其他方式协商出音视频的信息,然后自己在去创建解码器。
av_register_all() 方法要全局注册一次,里面注册编码解码器,以及解复用协议
10. 其他
- 代码 github.com/SnailCoderG…
- 有问题联系我:
- 微信: p13071210551
- 邮箱: gu19860621@163.com