ffmpeg 视频解码一

1. ffmpeg 视频解码一
2. ffmpeg 视频解码二
3. ffmpeg 音频解码一
4. ffmpeg 音频解码二
5. ffmpeg 音视频解码
6. ffmpeg 视频编码一
7. ffmpeg 视频编码一(精简版)
8. ffmpeg 视频编码二(基于 libswscale 转换视频)
9. ffmpeg 过滤器libavfilter的使用
10. ffmpeg 视频编码三(基于 libavfilter 转换视频)

前言

ffmpeg的大版本都已经更新到4了,近段时间我也研究了一下,从这篇文章开始,我将写一个系列,阐述一下ffmpeg API的使用。这篇文章为第一篇,就仅仅讲述一下视频解码相关的使用吧(视频解码会写两篇,这为第一篇,分别使用不同的API)。

流程图

在这里插入图片描述
代码流程即如流程图所示,下面讲解一下当中部分函数的作用。

  1. av_parser_init
    这是一个解析器,我们根据解码器,实例化这个解析器,后面解析数据时使用。
  2. av_parser_parse2
    我们从输入文件得到的原始数据(不适用ffmpeg自带的api的话),直接使用是不行的,此时我们就需要把这个原始数据使用上面实例化的解析器来解析,把数据分割成帧,为后面解码数据做准备。
  3. avcodec_send_packet
    发送我们刚刚得到的解析数据到解码器做解码。
  4. avcodec_receive_frame
    获取解码之后的数据。

源码


#pragma once
#define __STDC_CONSTANT_MACROS
#define _CRT_SECURE_NO_WARNINGS

extern "C"
{
#include "libavcodec/avcodec.h"
}

#define INBUF_SIZE 4096

using namespace std;

#define INPUT_FILE_NAME "lh_online.h264"
#define OUTPUT_FILE_NAME "lh_online.yuv"


static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt,
	FILE* ofile)
{
	int ret;
	int y_size;
	ret = avcodec_send_packet(dec_ctx, pkt);
	if (ret < 0) {
		av_log(NULL, AV_LOG_ERROR, "发送数据包到解码器出错。\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_frame(dec_ctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			av_log(NULL, AV_LOG_ERROR, "Error sending a packet for decoding.\n");
			exit(1);
		}
		//此时一帧视频已经保存到frame中了
		// 
		//打印输出的视频帧的帧数
		av_log(NULL, AV_LOG_INFO, "saving frame:%d , width: %d ,height: %d.\n", dec_ctx->frame_number, frame->width, frame->height);
		//获取一帧视频数据大小
		y_size = frame->width * frame->height;
		fwrite(frame->data[0], 1, y_size, ofile);    //Y
		fwrite(frame->data[1], 1, y_size / 4, ofile);  //U
		fwrite(frame->data[2], 1, y_size / 4, ofile);  //V
	}
}

int main(int argc, char* argv[])
{
	const AVCodec* codec;
	AVCodecParserContext* parser;
	AVCodecContext* c = NULL;
	FILE* ifile, * ofile;
	AVFrame* frame;
	AVPacket* pkt;
	uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	uint8_t* data;
	size_t   data_size;
	int ret;

	//初始化inbuf数组
	memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);

	//获取解码器(此处需要读取的文件是H264,故)
	codec = avcodec_find_decoder(AV_CODEC_ID_H264);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "Codec not found.\n");
		exit(1);
	}

	//注册解析器
	parser = av_parser_init(codec->id);
	if (!parser) {
		av_log(NULL, AV_LOG_ERROR, "parser not found.\n");
		exit(1);
	}

	//分配解析器上下文
	c = avcodec_alloc_context3(codec);
	if (!c) {
		av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.\n");
		exit(1);
	}

	//打开解码器
	if (avcodec_open2(c, codec, NULL) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not open codec.\n");
		exit(1);
	}

	//分配AVPacket
	pkt = av_packet_alloc();
	if (!pkt) {
		exit(1);
	}

	//分配AVFrame
	frame = av_frame_alloc();
	if (!frame) {
		exit(1);
	}

	//打开输入文件
	ifile = fopen(INPUT_FILE_NAME, "rb");
	if (!ifile) {
		av_log(NULL, AV_LOG_ERROR, "Could not open \s.\n", INPUT_FILE_NAME);
		exit(1);
	}
	//打开输入文件
	ofile = fopen(OUTPUT_FILE_NAME, "wb+");
	if (!ofile) {
		av_log(NULL, AV_LOG_ERROR, "Could not open \s.\n", OUTPUT_FILE_NAME);
		exit(1);
	}


	while (!feof(ifile)) {
		//从输入流 ifile 读取数据到 inbuf 所指向的数组中
		data_size = fread(inbuf, 1, INBUF_SIZE, ifile);
		if (!data_size)
			break;

		//使用注册的解析器 parser 把数据分割成帧
		data = inbuf;
		while (data_size > 0) {
			ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
				data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
			if (ret < 0) {
				av_log(NULL, AV_LOG_ERROR, "Error while parsing.\n");
				exit(1);
			}
			//根据使用情况重置数据位置
			data += ret;
			data_size -= ret;

			if (pkt->size) {
				decode(c, frame, pkt, ofile);
			}
		}
	}

	//flush 解码器
	decode(c, frame, NULL, ofile);

	//资源释放
	fclose(ifile);
	fclose(ofile);

	av_parser_close(parser);
	avcodec_free_context(&c);
	av_frame_free(&frame);
	av_packet_free(&pkt);

	return 0;
}

这个示例中我们把一个H264编码的 lh_online.h264 的文件解码成原始YUV视频文件 lh_online.yuv(这里不做YUV的介绍)。
下面我们使用ffplay来播放我们刚刚解码出来的视频原始数据(播放路径以自己实际路劲为准)。

ffplay -i C:\...\lh_online.yuv -pix_fmt yuv420p -s 512*288

注意两点:

  1. 此时我们播放的数据格式是 yuv 的(此处可设置可不设置,ffplay默认可播放)。
  2. 我们需要知道视频的原始宽高,代码里有个位置获取了宽高的(dec_ctx->frame_number, frame->width, frame->height),我们需要把快其设置到命令里,否者是不能播放的(我这个文件是512*288的)。

下面看下效果。
在这里插入图片描述

到此,基于parser解析器的解码就完成了,我们任何知道文件格式的视频,如果直接基于文件读取的方式都可以使用这种方式。
下一篇将讲述纯基于API的方式,应该是比这个方便很多。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lhuann_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值