FFmpeg 视频解码(秒懂)

1.简介

解码如下图所示,将H.264数据解码为YUV。

2.流程

 2.1 在使用FFmpeg API之前,需要先注册API,然后才能使用API。当然,新版本的库不需要再调用下面的方法。

av_register_all()

2.2 构建输入AVFormatContext

声明输入的封装结构体,通过输入文件或者流地址作为封装结构的句柄。

    AVFormatContext* ifmt_ctx = NULL;
	const char* inputUrl = "test.mp4";

	///打开输入的流
	int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

2.3查找音视频流信息,通过下面的接口与AVFormatContext中建立输入文件对应的流信息。


    //查找;
    if (avformat_find_stream_info(inputFmtCtx, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

2.4查找解码器

先找到视频流索引,找到视频流,根据视频流的codec_id找到解码器。

    //找到视频流索引
    int video_index =  av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    AVStream* st = ifmt_ctx->streams[video_index];

    AVCodec* codec = nullptr;

    //找到解码器
    codec = avcodec_find_decoder(st->codecpar->codec_id);
    if (!codec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

2.5申请AVCodecContenxt

    //申请AVCodecContext
    AVCodecContext* codec_ctx = nullptr;
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        exit(1);
    }

2.6同步AVCodecParameters

FFmpeg在解码时获取的相关编码信息,首先存储到AVCodecParameters中,然后对AVCodecParameters中存储的信息进行解析与处理,为了兼容,将AVCodecParameters的参数同步到AVCodecContext中。

avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[video_index]->codecpar);

2.7打开解码器

解码器参数设置完成后,就需要打开解码器

    //打开解码器
    if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
    {
        return -1;
    }

2.8然后通过while循环,不停的读取数据

av_read_frame(ifmt_ctx, pkt)

2.9帧解码

通过下面两个接口解码,获取到解码后的帧数据。

avcodec_send_packet(codec_ctx, pkt);

avcodec_receive_frame(codec_ctx, frame);

3.源码

此源码演示解码后的数据保存到本地,以yuv为结尾的文件。

#include "pch.h"
#include <iostream>

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h" 
};

int main()
{
	//av_register_all();
	avformat_network_init();

    AVFormatContext* ifmt_ctx = NULL;
	const char* inputUrl = "test.mp4";

	///打开输入的流
	int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
	if (ret != 0)
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}

	//查找流信息
	if (avformat_find_stream_info(ifmt_ctx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}

	//找到视频流索引
    int video_index =  av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    AVStream* st = ifmt_ctx->streams[video_index];

    AVCodec* codec = nullptr;

    //找到解码器
    codec = avcodec_find_decoder(st->codecpar->codec_id);
    if (!codec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    //申请AVCodecContext
    AVCodecContext* codec_ctx = nullptr;
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        exit(1);
    }

	avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[video_index]->codecpar);

    //打开解码器
    if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
    {
        return -1;
    }

	AVPacket* pkt = av_packet_alloc();
	//av_init_packet(pkt);

	AVFrame *frame = av_frame_alloc();

	while (av_read_frame(ifmt_ctx, pkt) >= 0)
	{
		if (pkt->stream_index == video_index)
		{
			int ret = avcodec_send_packet(codec_ctx, pkt);
			if (ret >= 0)
			{
				ret = avcodec_receive_frame(codec_ctx, frame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
				{
					break;
				}
				else if (ret < 0)
				{
					break;
				}

				switch (codec_ctx->pix_fmt)
				{
					case AV_PIX_FMT_YUV420P:
					{

						int size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, frame->width, frame->height, 1);
						uint8_t* yuvbuf = (uint8_t*)av_malloc(size);

						int i, j, k = 0;
						for (i = 0; i < frame->height; i++)
						{
							memcpy(yuvbuf + frame->width * i, frame->data[0] + frame->linesize[0] * i, frame->width);
						}
						for (j = 0; j < frame->height / 2; j++)
						{
							memcpy(yuvbuf + frame->width * i + frame->width / 2 * j,frame->data[1] + frame->linesize[1] * j,frame->width / 2);
						}
						for (k = 0; k < frame->height / 2; k++)
						{
							memcpy(yuvbuf + frame->width * i + frame->width / 2 * j + frame->width / 2 * k,frame->data[2] + frame->linesize[2] * k,frame->width / 2);
						}

						char fileName[20] = { 0 };
						sprintf(fileName, "img/%d.yuv", codec_ctx->frame_number);

						FILE* f;
						f = fopen(fileName, "wb");
	
						fwrite(yuvbuf,1, size,f);
						fclose(f);

						av_free(yuvbuf);
					}
					break;
					default:
						return -1;
					}
			}
		}
	}

	avcodec_close(codec_ctx);
	avcodec_free_context(&codec_ctx);
	avformat_close_input(&ifmt_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);

    return 0;
}

4.yuv查看工具

https://github.com/IENT/YUView/releases

猜你喜欢

转载自blog.csdn.net/wzz953200463/article/details/125861101