视频解码,并且输出到文件。
我通过雷霄骅的博客学习FFMPEG,在学习过程中发现“雷神”的代码由于版本的问题,很多代码已经无法在FFMPEG——4.2.2版本中使用,而在网上也有很多教程是基于FFMPEG——3.x.x版本的,因此,特别写了这篇文章,解决学习“雷神”博客代码在4.x.x上编译无法通过的问题。本篇文章最下面的代码,配置好环境之后,直接复制,然后修改输入视频文件的路径,就可以运行。
特别是在4.x.x版本中av_register_all();这个函数完全不需要。
本程序的输入文件只要是编码数据是H264;AAC,解码数据是YUV420P;PCM格式的都可以正常运行。
本程序输出的文件格式是:H264;YUV420P;AAC;PCM。
比较大的改动举例:
雷霄骅的例子代码如下:
pFrameYUV=av_frame_alloc();
out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
这些在新版本无法编译,很多声明已经否决。
我修改后的初始化代码如下:
AVFrame* p_z_p_deco_AVFrame = av_frame_alloc();
p_z_p_deco_AVFrame->width = p_input_file_v_AVCodecContext->width;
p_z_p_deco_AVFrame->height = p_input_file_v_AVCodecContext->height;
p_z_p_deco_AVFrame->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(p_z_p_deco_AVFrame, 4);
以下是ffmpeg4.2.2版本中对于AVFrame结构体的说明。
/**
* Allocate an AVFrame and set its fields to default values. The resulting
* struct must be freed using av_frame_free().
*
* @return An AVFrame filled with default values or NULL on failure.
*
* @note this only allocates the AVFrame itself, not the data buffers. Those
* must be allocated through other means, e.g. with av_frame_get_buffer() or
* manually.
*/
AVFrame *av_frame_alloc(void);
雷霄骅的例子使用的函数如下:
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
int *got_picture_ptr,
const AVPacket *avpkt);
FFMPEG——4.2.2版本用的解码的函数是:
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
FFMPEG——4.2.2版本用的编码函数是:
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
使用如下函数读取一帧数据以后,*pkt指向的内存空间在读取的过程中不能直接释放,需要使用void av_packet_unref(AVPacket *pkt)来减少引用,在左后所有数据读完之后,才用void av_packet_free(AVPacket **pkt)函数释放掉空间。
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
其它的有关版本问题就不一一列举,最后推荐初学者去看看《基于FFmpeg+SDL的视频播放器的制作——雷霄骅》这些视频,有利于学习FFMPEG。本段代码是基于视频中前四小节内容的更新。
感谢雷霄骅。
/**
解码实例
时间:2020年4月
ffmpeg版本:4.2.2
vs版本:Microsoft Visual Studio Enterprise 2019
开发语言:C++
特别声明:
本程序是我通过学习雷霄骅 Lei Xiaohua的博客而编写的,因此,在此向雷霄骅表示敬意。
雷霄骅的博客地址:https://blog.csdn.net/leixiaohua1020
所需知识背景:
“视音频数据处理入门:RGB、YUV像素数据处理”,链接地址:https://blog.csdn.net/leixiaohua1020/article/details/50534150
“视音频数据处理入门:PCM音频采样数据处理”,链接地址:https://blog.csdn.net/leixiaohua1020/article/details/50534316
“视音频数据处理入门:H.264视频码流解析”,链接地址:https://blog.csdn.net/leixiaohua1020/article/details/50534369
“视音频数据处理入门:AAC音频码流解析”,链接地址:https://blog.csdn.net/leixiaohua1020/article/details/50535042
说明:本程序实现了解码本地视频,并且分别输出解码的音视频文件到文件。
通过本程序可以了解整个ffmpeg最简单的解码流程。程序为了方便阅读,没有采用面向对象编程。
本程序能完成雷霄骅的《基于FFmpeg+SDL的视频播放器的制作——雷霄骅》视频前四小节的内容。
*/
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
}
#include
#include
using namespace std;
int main() {
//本地输入的文件路径
const char* p_intputFilePath = "sintel.ts";
if(p_intputFilePath == NULL) {
cout << "请输入文件路径!" << endl;
return -1;
}
//获取文件格式上下文
AVFormatContext* p_inptu_file_AVFormatContext = avformat_alloc_context();
//打开文件
if(avformat_open_input(&p_inptu_file_AVFormatContext, p_intputFilePath, NULL, NULL) != 0) {
cout << "文件打开失败" << endl;
return -1;
};
//获取文件流信息
if(avformat_find_stream_info(p_inptu_file_AVFormatContext, NULL) < 0) {
cout << "获取信息失败" << endl;
return -1;
}
//获取到音频,视频索引
int audioIndex = av_find_best_stream(p_inptu_file_AVFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, NULL);
int vedioIndex = av_find_best_stream(p_inptu_file_AVFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, NULL);
//找到音频流和视频流
AVStream* p_input_file_a_AVStream = p_inptu_file_AVFormatContext->streams[audioIndex];
AVStream* p_input_file_v_AVStream = p_inptu_file_AVFormatContext->streams[vedioIndex];
//获取到解码器
AVCodec* p_input_file_a_AVCodec = avcodec_find_decoder(p_input_file_a_AVStream->codecpar->codec_id);
AVCodec* p_input_file_v_AVCodec = avcodec_find_decoder(p_input_file_v_AVStream->codecpar->codec_id);
//获取音频,视频的AVCodecContext。特别注意,ffmpeg4.2.2版本提倡使用AVCodecParameters。
AVCodecContext* p_input_file_a_AVCodecContext = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(p_input_file_a_AVCodecContext, p_input_file_a_AVStream->codecpar);
AVCodecContext* p_input_file_v_AVCodecContext = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(p_input_file_v_AVCodecContext, p_input_file_v_AVStream->codecpar);
//是否含有视频流和音频流(一般都包含)
if(audioIndex < 0) {
cout << "此文件不包含音频流或者没有解码器" << endl;
}
if(vedioIndex < 0) {
cout << "此文件不包含视频流或者没有解码器" << endl;
}
//打开音频,视频的解码器
if(avcodec_open2(p_input_file_a_AVCodecContext, p_input_file_a_AVCodec, NULL) < 0) {
cout << "音频解码器打开失败!" << endl;
return -1;
}
if(avcodec_open2(p_input_file_v_AVCodecContext, p_input_file_v_AVCodec, NULL) < 0) {
cout << "视频解码器代开失败!" << endl;
return -1;
}
cout << "-------------------------------------- 输出信息开始位置 ---------------------------------" << endl;
av_dump_format(p_inptu_file_AVFormatContext, 0, p_intputFilePath, 0);
cout << "-------------------------------------- 输出信息结束位置 ---------------------------------" << endl;
//读取一帧数据存放空间
AVPacket* p_read_frame_AVPacket = av_packet_alloc();
//解码一帧数据存放空间
AVFrame* p_deco_AVFrame = av_frame_alloc();
//转码一帧数据存放空间
AVFrame* p_z_p_deco_AVFrame = av_frame_alloc();
p_z_p_deco_AVFrame->width = p_input_file_v_AVCodecContext->width;
p_z_p_deco_AVFrame->height = p_input_file_v_AVCodecContext->height;
p_z_p_deco_AVFrame->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(p_z_p_deco_AVFrame, 4);
//转码格式设置
SwsContext* img_convert_ctx = NULL;
//视频编码的数据输出到out_file_h264.h264文件
ofstream out_file_h264("out_file_h264.h264", ios::binary);
//视频解码的数据输出到out_file_yuv240p.yuv
ofstream out_file_yuv240p("out_file_yuv240p.yuv", ios::binary);
//音频编码的数据输出到out_file_aac.pcm
ofstream out_file_acc("out_file_aac.aac", ios::binary);
//音频解码的数据输出到out_file_pcm.pcm
ofstream out_file_pcm("out_file_pcm.pcm", ios::binary);
int re = 0;
int v_lenth = p_input_file_v_AVCodecContext->width * p_input_file_v_AVCodecContext->height;
while(av_read_frame(p_inptu_file_AVFormatContext, p_read_frame_AVPacket) >= 0) {
//输出编码数据
if(p_read_frame_AVPacket->stream_index == vedioIndex) {
//输出视频编码数据
out_file_h264.write((char*) p_read_frame_AVPacket->data, p_read_frame_AVPacket->size);
//解码视频
re = avcodec_send_packet(p_input_file_v_AVCodecContext, p_read_frame_AVPacket);
if(re == 0) {
while(avcodec_receive_frame(p_input_file_v_AVCodecContext, p_deco_AVFrame) == 0) {
//这里需要转换一下数据
img_convert_ctx = sws_getContext(p_deco_AVFrame->width, p_deco_AVFrame->height, p_input_file_v_AVCodecContext->pix_fmt,
p_input_file_v_AVCodecContext->width, p_input_file_v_AVCodecContext->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, p_deco_AVFrame->data, p_deco_AVFrame->linesize, 0, p_input_file_v_AVCodecContext->height,
p_z_p_deco_AVFrame->data, p_z_p_deco_AVFrame->linesize);
//输出视频解码数据
out_file_yuv240p.write((char*) p_z_p_deco_AVFrame->data[0], v_lenth);
out_file_yuv240p.write((char*) p_z_p_deco_AVFrame->data[1], v_lenth / 4);
out_file_yuv240p.write((char*) p_z_p_deco_AVFrame->data[2], v_lenth / 4);
}
}else {
cout << "视频解解码失败" << endl;
break;
}
}else {
//输出音频编码数据
out_file_acc.write((char*) p_read_frame_AVPacket->data, p_read_frame_AVPacket->size);
//解码音频
re = avcodec_send_packet(p_input_file_a_AVCodecContext, p_read_frame_AVPacket);
if(re == 0) {
while(avcodec_receive_frame(p_input_file_a_AVCodecContext, p_deco_AVFrame) == 0) {
//输出音频解码数据
for(int i = 0; i < p_deco_AVFrame->nb_samples; i++) {
for(int j = 0; j < p_deco_AVFrame->channels; j++) {
out_file_pcm.write((char*) p_deco_AVFrame->data[j], av_get_bytes_per_sample(p_input_file_a_AVCodecContext->sample_fmt));
}
}
}
}else {
cout << "音频解码失败" << endl;
break;
}
}
//减少引用
av_packet_unref(p_read_frame_AVPacket);
}
//释放内存
out_file_pcm.close();
out_file_acc.close();
out_file_yuv240p.close();
out_file_h264.close();
av_frame_free(&p_z_p_deco_AVFrame);
av_frame_free(&p_deco_AVFrame);
av_packet_free(&p_read_frame_AVPacket);
avcodec_free_context(&p_input_file_a_AVCodecContext);
avcodec_free_context(&p_input_file_v_AVCodecContext);
avformat_free_context(p_inptu_file_AVFormatContext);
给出观看雷霄骅录制的视频地址:基于FFmpeg+SDL的视频播放器的制作——雷霄骅