使用FFMPEG——4.2.2版本实现提取视频编码解码文件,ffmpeg基础学习。

FFMPEG基础学习

视频解码,并且输出到文件。

我通过雷霄骅的博客学习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。

比较大的改动举例:

1、关于AVFrame结构体在转码过程中的初始化:

雷霄骅的例子代码如下:

    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);

2、关于编码和解码

雷霄骅的例子使用的函数如下:

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);

3、关于在解码过程中AVPacket的内存释放问题

使用如下函数读取一帧数据以后,*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 <iostream>
#include <fstream>
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的视频播放器的制作——雷霄骅

发布了5 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/zhucehenfanren/article/details/105363443