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的第二篇,这篇主要实现一下使用ffmpeg的API实现解码,不使用和上篇的解析器做解析。

流程图

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

  1. avformat_open_input
    打开输入文件,并读取文件头相关信息
  2. avformat_find_stream_info
    读取媒体文件信息。
  3. av_find_best_stream
    获取视频流序号(因为文件当中可能既有音频也有视频,字幕等流,我们这里使用这个函数获取视频流的序号)。
  4. avcodec_find_decoder
    获取解码器
  5. avcodec_parameters_to_context
    我们自己构建的解码器并没有设置一些解码相关的参数,此时我们拷贝视频流的参数到里面即可。
  6. av_read_frame
    从视频当中读取数据(一帧),不用和上篇一样,还需要我们使用解析器解析成一帧。
  7. avcodec_send_packet
    发送我们刚刚得到的解析数据到解码器做解码。
  8. avcodec_receive_frame
    获取解码之后的数据。

源码


#pragma once
#define __STDC_CONSTANT_MACROS
#define _CRT_SECURE_NO_WARNINGS

extern "C"
{
#include <libavformat/avformat.h>
#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;
	AVFormatContext* fmt_ctx = NULL;
	AVCodecContext* c = NULL;
	AVStream* st;
	AVFrame* frame;
	AVPacket* pkt;
	int ret;
	int stream_index;

	FILE* ofile;


	//打开输入文件,并为fmt_ctx分配空间
	if (avformat_open_input(&fmt_ctx, INPUT_FILE_NAME, NULL, NULL)) {
		av_log(NULL, AV_LOG_ERROR, "Codec not open source file.\n");
		exit(1);
	}

	//获取流信息
	if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not find stream information.\n");
		exit(1);
	}

	//获取视频流序号(这里我们明确要解码的是视频,也只处理视频)
	stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (stream_index < 0) {
		av_log(NULL, AV_LOG_ERROR, "Cannot find stream index\n");
		exit(1);
	}

	//获取文件流
	st = fmt_ctx->streams[stream_index];

	//获取解码器(这里不需要我们显示的指定了)
	codec = avcodec_find_decoder(st->codecpar->codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "Codec 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_parameters_to_context(c, st->codecpar) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Failed to copy %s codec parameters to decoder 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);
	}

	//打开输出文件
	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 (av_read_frame(fmt_ctx, pkt) >= 0) {
		//只处理视频流
		if (pkt->stream_index==stream_index) {
			decode(c, frame, pkt, ofile);
		}
		av_packet_unref(pkt);
	}

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

	//资源释放
	fclose(ofile);

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

	return 0;
}

这个示例中和上篇文章一样,还是把一个H264编码的 lh_online.h264 的文件解码成原始YUV视频文件 lh_online.yuv
播放还是使用ffplay来实现,下面是播放的代码。

ffplay -i  D:\project\ffmpeg_demo\decode_video_by_api\lh_online.yuv -pix_fmt yuv420p -s 512*288

注意两点:

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

下面看下效果。

在这里插入图片描述
到此,视频解码相关的API即介绍完了。

相对于上篇文章基于parser的方式来的话,这篇文章的相对来说代码逻辑是比较直观的,也不需要我们自己去考虑分割视频帧相关的问题(平常我们使用最多的应该也是这种方式)。

需要注意的就是我们需要 avformat.h这个文件头,这个文件封装的里面即是媒体文件格式相关的API。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lhuann_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值