[ffmpeg系列 03] 文件、流地址(视频)解码为YUV

一  代码

ffmpeg版本5.1.2,dll是:ffmpeg-5.1.2-full_build-shared。x64的。

文件、流地址对使用者来说是一样。
流地址(RTMP、HTTP-FLV、RTSP等):信令完成后,才进行音视频传输。信令包括音视频格式、参数等协商。

接流的在实际中的应用:1 展示,播放。2 给算法用,一般是需要RGB格式的。

#ifndef _DECODE_H264_H_
#define _DECODE_H264_H_
#include <string>

extern  "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavutil/pixdesc.h"
#include "libavutil/display.h"
};

#pragma  comment(lib, "avformat.lib")
#pragma  comment(lib, "avutil.lib")
#pragma  comment(lib, "avcodec.lib")
#pragma  comment(lib, "swscale.lib")


class CDecodeH264
{
public:
	CDecodeH264();
	~CDecodeH264();

public:


public:
	int DecodeH264();
	int  Start();
	int  Close();

	int  DecodeH264File_Init();
	int  ReleaseDecode();
	void H264Decode_Thread_Fun();
	std::string  dup_wchar_to_utf8(const wchar_t* wstr);
	double get_rotation(AVStream *st);

public:
	AVFormatContext*       m_pInputFormatCtx = nullptr;
	AVCodecContext*        m_pVideoDecodeCodecCtx = nullptr;
	const AVCodec*           m_pCodec = nullptr;	
	SwsContext*                m_pSwsContext = nullptr;
	AVFrame*                    m_pFrameScale = nullptr;
	AVFrame*                    m_pFrameYUV = nullptr;
	AVPacket*                   m_pAVPacket = nullptr;
	
	enum AVMediaType		m_CodecType;
	int                                    m_output_pix_fmt;
	int                                    m_nVideoStream = -1;
	int                                    m_nFrameHeight = 0;
	int                                    m_nFrameWidth = 0;
	int                                    m_nFPS;
	int                                    m_nVideoSeconds;

	FILE*         m_pfOutYUV = nullptr;
	FILE*         m_pfOutYUV2 = nullptr;
};
#endif



#include "DecodeH264.h"
#include <thread>
#include <functional>
#include <codecvt>
#include <locale>


char av_error2[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str2(errnum) av_make_error_string(av_error2, AV_ERROR_MAX_STRING_SIZE, errnum)


CDecodeH264::CDecodeH264()
{	
}


CDecodeH264::~CDecodeH264()
{
	ReleaseDecode();
}

std::string  CDecodeH264::dup_wchar_to_utf8(const wchar_t* wstr)
{
	std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
	return converter.to_bytes(wstr);
}

//Side data : 
//displaymatrix: rotation of - 90.00 degrees
double CDecodeH264::get_rotation(AVStream *st)
{
	uint8_t* displaymatrix = av_stream_get_side_data(st,
		AV_PKT_DATA_DISPLAYMATRIX, NULL);
	double theta = 0;
	if (displaymatrix)
		theta = -av_display_rotation_get((int32_t*)displaymatrix);

	theta -= 360 * floor(theta / 360 + 0.9 / 360);

	if (fabs(theta - 90 * round(theta / 90)) > 2)
		av_log(NULL, AV_LOG_WARNING, "Odd rotation angle.\n"
			"If you want to help, upload a sample "
			"of this file to https://streams.videolan.org/upload/ "
			"and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)");

	return theta;
}

int CDecodeH264::DecodeH264File_Init()
{
    avformat_network_init(); //流地址需要

	m_pInputFormatCtx = avformat_alloc_context();

	
	std::string strFilename = dup_wchar_to_utf8(L"测试.h264");
	//std::string strFilename = dup_wchar_to_utf8(L"rtmp://127.0.0.1/live/now");	
	int ret = avformat_open_input(&m_pInputFormatCtx, strFilename.c_str(), nullptr, nullptr);
	if (ret != 0) {
		char* err_str = av_err2str2(ret);
		printf("fail to open filename: %s, return value: %d,  %s\n", strFilename.c_str(), ret, err_str);
		return -1;
	}

	ret = avformat_find_stream_info(m_pInputFormatCtx, nullptr);
	if (ret < 0) {
		char* err_str = av_err2str2(ret);
		printf("fail to get stream information: %d, %s\n", ret, err_str);		
		return -1;
	}


	for (int i = 0; i < m_pInputFormatCtx->nb_streams; ++i) {
		const AVStream* stream = m_pInputFormatCtx->streams[i];
		if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			m_nVideoStream = i;
			printf("type of the encoded data: %d, dimensions of the video frame in pixels: width: %d, height: %d, pixel format: %d\n",
				stream->codecpar->codec_id, stream->codecpar->width, stream->codecpar->height, stream->codecpar->format);
		}
	}
	if (m_nVideoStream == -1) {
		printf("no video stream\n");
		return -1;
	}
	printf("m_nVideoStream=%d\n", m_nVideoStream);

	

	
    //获取旋转角度
	double theta = get_rotation(m_pInputFormatCtx->streams[m_nVideoStream]);
	

	m_pVideoDecodeCodecCtx = avcodec_alloc_context3(nullptr);
	avcodec_parameters_to_context(m_pVideoDecodeCodecCtx,\
             m_pInputFormatCtx->streams[m_nVideoStream]->codecpar);
	m_pCodec = avcodec_find_decoder(m_pVideoDecodeCodecCtx->codec_id);
	if (m_pCodec == nullptr)
	{
		return -1;
	}

	m_nFrameHeight = m_pVideoDecodeCodecCtx->height;
	m_nFrameWidth = m_pVideoDecodeCodecCtx->width;
	printf("w=%d h=%d\n", m_pVideoDecodeCodecCtx->width, m_pVideoDecodeCodecCtx->height);
	

	if (avcodec_open2(m_pVideoDecodeCodecCtx, m_pCodec, nullptr) < 0)
	{
		return -1;
	}

	//读文件知道视频宽高
	m_output_pix_fmt = AV_PIX_FMT_YUV420P; //AV_PIX_FMT_NV12;
	m_pSwsContext = sws_getContext(m_pVideoDecodeCodecCtx->width, m_pVideoDecodeCodecCtx->height,
		m_pVideoDecodeCodecCtx->pix_fmt, m_pVideoDecodeCodecCtx->width, m_pVideoDecodeCodecCtx->height,
		(AVPixelFormat)m_output_pix_fmt, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);



	//解码后的视频数据
	m_pFrameScale = av_frame_alloc();
	m_pFrameScale->format = m_output_pix_fmt;

	m_pFrameYUV = av_frame_alloc();
	m_pFrameYUV->format = m_output_pix_fmt; //mAVFrame.format is not set
	m_pFrameYUV->width = m_pVideoDecodeCodecCtx->width;
	m_pFrameYUV->height = m_pVideoDecodeCodecCtx->height;
	printf("m_pFrameYUV pix_fmt=%d\n", m_pVideoDecodeCodecCtx->pix_fmt);

	av_frame_get_buffer(m_pFrameYUV, 64);
	

	char cYUVName[256];
	sprintf(cYUVName, "%d_%d_%s.yuv", m_nFrameWidth, m_nFrameHeight, av_get_pix_fmt_name(m_pVideoDecodeCodecCtx->pix_fmt));
	fopen_s(&m_pfOutYUV, cYUVName, "wb");

	char cYUVName2[256];
	sprintf(cYUVName2, "%d_%d_%s_2.yuv", m_nFrameWidth, m_nFrameHeight, av_get_pix_fmt_name(m_pVideoDecodeCodecCtx->pix_fmt));
	fopen_s(&m_pfOutYUV2, cYUVName2, "wb");

	printf("leave init\n");

	return   0;
}


void CDecodeH264::H264Decode_Thread_Fun()
{
	int   nFrameFinished = 0;
	int i = 0;
	int  ret;
	m_pAVPacket = av_packet_alloc();

	while (true) {
		ret = av_read_frame(m_pInputFormatCtx, m_pAVPacket);
	
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
				av_packet_unref(m_pAVPacket);
				printf("read_frame break");
				break;
			}

		if (m_pAVPacket->stream_index == m_nVideoStream)
		{

			int  send_packet_ret = avcodec_send_packet(m_pVideoDecodeCodecCtx, m_pAVPacket);
			printf("encode video send_packet_ret %d\n", send_packet_ret);
			int receive_frame_ret = avcodec_receive_frame(m_pVideoDecodeCodecCtx, m_pFrameScale);
			char* err_str = av_err2str2(receive_frame_ret);
			printf("frame w=%d, h=%d, linesize[0]=%d, linesize[1]=%d\n", m_pFrameScale->width, m_pFrameScale->height, m_pFrameScale->linesize[0], m_pFrameScale->linesize[1]);
			
			

			if (receive_frame_ret == 0)
			{
				++i;

				int iReturn = sws_scale(m_pSwsContext, m_pFrameScale->data,
					m_pFrameScale->linesize, 0, m_nFrameHeight,
					m_pFrameYUV->data, m_pFrameYUV->linesize);
				printf("frame w=%d, h=%d, linesize[0]=%d, linesize[1]=%d\n", m_pFrameYUV->width, m_pFrameYUV->height, m_pFrameYUV->linesize[0], m_pFrameYUV->linesize[1]);
				/*if (0 != iReturn)
				{
					fwrite(m_pFrameYUV->data[0], 1, m_nFrameWidth * m_nFrameHeight, m_pfOutYUV);
					fwrite(m_pFrameYUV->data[1], 1, m_nFrameWidth * m_nFrameHeight /4, m_pfOutYUV);
					fwrite(m_pFrameYUV->data[2], 1, m_nFrameWidth * m_nFrameHeight /4, m_pfOutYUV);
				}*/
				//用linesize更能兼容特殊的宽
				if (0 != iReturn)
				{					
					for (int i = 0; i < m_nFrameHeight; ++i) {
						fwrite(m_pFrameYUV->data[0] + i * m_pFrameYUV->linesize[0], 1, m_nFrameWidth, m_pfOutYUV2);
					}
					for (int i = 0; i < m_nFrameHeight / 2; ++i) {
						fwrite(m_pFrameYUV->data[1] + i * m_pFrameYUV->linesize[1], 1, m_nFrameWidth / 2, m_pfOutYUV2);
					}
					for (int i = 0; i < m_nFrameHeight / 2; ++i) {
						fwrite(m_pFrameYUV->data[2] + i * m_pFrameYUV->linesize[2], 1, m_nFrameWidth / 2, m_pfOutYUV2);
					}
				}				

			}
		}
		av_packet_unref(m_pAVPacket);
	}
}


int CDecodeH264::DecodeH264()
{
	if (DecodeH264File_Init() != 0)
	{
		return   -1;
	}

	auto video_func = std::bind(&CDecodeH264::H264Decode_Thread_Fun, this);
	std::thread  video_thread(video_func);
	video_thread.join(); 

	return 0;
}


int CDecodeH264::Start()
{
	DecodeH264();
	
	return  1;
}



int CDecodeH264::Close()
{
	return   0;
}


int  CDecodeH264::ReleaseDecode()
{
	if (m_pSwsContext)
	{
		sws_freeContext(m_pSwsContext);
		m_pSwsContext = nullptr;
	}
	if (m_pFrameScale)
	{
		av_frame_free(&m_pFrameScale);//av_frame_alloc()对应
	}

	if (m_pFrameYUV)
	{
		av_frame_free(&m_pFrameYUV);
	}

	avcodec_close(m_pVideoDecodeCodecCtx);

	avformat_close_input(&m_pInputFormatCtx);


	return 0;

}
#include <iostream>
#include <Windows.h>

#include "1__DecodeH264/DecodeH264.h"


int main()
{

    CDecodeH264* m_pDecodeVideo = new CDecodeH264();
	m_pDecodeVideo->Start();


    return 0;
}

图是雷神博客的:注册函数废弃了,解码函数变了。

二  相关的结构体,方便记忆


1 AVFrame是未压缩的,解码后的数据。
  AVPacket是压缩的,解码前的数据。
  知道了这个,编码的send_frame、receive_packet,解码的send_packet、receive_frame,容易记住了。

2 2个Context(上下文):Format(混合文件、流地址)、Codec(单个编码格式,比如H264、AAC,编解码实现)
  AVFormatContext*  m_pInputFormatCtx;
  AVCodecContext*   m_pVideoDecodeCodecCtx;
  m_pInputFormatCtx会用到的函数:avformat_open_input、avformat_find_stream_info、 
  av_read_frame、avformat_close_input。
  m_pOutputFormatCtx会用到的函数:avcodec_find_decoder、avcodec_open2、
   avcodec_send_packet、 avcodec_receive_frame。


3 AVCodec结构体
const  AVCodec  ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    ……
}

static const AVCodec * const codec_list[] = {
...
  &ff_h264_decoder,
...
};

三 兼容性问题

1 文件名带中文,需要转换。

2 播放竖屏视频(手机录的那种),获取旋转角度。

截图是ffmpeg做法:获取角度后,使用filter调整。

3 宽比较特殊,不是16,32的整数。(比如544x960,544是32的倍数)。用linesize[i]代替宽。

linesize跟cpu有关,是cpu 16、32的倍数。

其它,待更新。

四  为什么需要sws_scale?转换到统一格式I420

sws_scale作用:1 分辨率缩放、 2 不同YUV、RGB格式转换。

H264有记录编码前的YUV采样格式,chroma_format_idc,在sps里。如果没有这个字段,说明该字段用的默认值1,即yuv 4:2:0。

如果YUV的采样格式是yuv 4:2:0,也不要求缩放,不需要sws_scale。

avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

frame->format记录了yuv的类型。

ffmpeg默认解码成:编码前的yuv格式。即m_pVideoDecodeCodecCtx->pix_fmt。

int ff_decode_frame_props(AVCodecContext *avctx, AVFrame *frame)
{
  ...
  frame->format              = avctx->pix_fmt;
  ...
}

五  不同格式的time_base

H264的time_base:1/1200000。

flv:音视频都是1/1000。

mp4:视频1/12800(帧率25,怎么算出来的?),音频:1/48000(1/采样频率)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/301088.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Spring实战】25 Spring Boot Admin 应用

文章目录 1. 查看健康信息2. 使用 Micrometer 和 "/metrics"3. 管理包和类的日志级别4. 其他功能总结 Spring Boot Admin 是一个功能强大的工具&#xff0c;用于监控和管理多个 Spring Boot 应用程序。通过上一篇文章 【Spring实战】24 使用 Spring Boot Admin 管理…

vue-springboot 音乐推荐系统 带歌词的音乐播放器系统设计与实现 7902c

少数民族音乐网站在流畅性&#xff0c;续航能力&#xff0c;等方方面面都有着很大的优势。这就意味着少数民族音乐网站的设计可以比其他系统更为出色的能力&#xff0c;可以更高效的完成最新的音乐信息、音乐资讯、在线交流等功能。 此系统设计主要采用的是JAVA语言来进行开发&…

240107-RHEL8+RHEL9配置安装:NVIDIA驱动(15步)+CUDA(4步)+CUDNN(5步)+GPU压力测试

Section 0: 基础知识 CUDA、cuDNN 和 PyTorch 版本的选择与搭配指南 安装优先级: 显卡驱动 → CUDA → CUDA Toolkit → cuDNN → Pytorch 即显卡驱动决定了CUDA版本&#xff0c;CUDA版本决定了CUDA Toolkit、cuDNN、Pytorch各自的版本提前下载 &#xff5c; CUDA提前下载 &am…

分布式系统架构设计之分布式消息队列基础知识

随着微服务、大数据和云计算的普及&#xff0c;分布式系统已经成为现代软件架构的核心。在分布式系统中&#xff0c;各个组件间的通信和数据交换尤其重要&#xff0c;而消息队列正是实现这一目标的关键技术之一。 在分布式架构设计过程中&#xff0c;架构师们需要对消息队列有…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于两阶段随机优化的电能量与深度调峰融合市场出清模型及定价方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的专栏栏目《论文与完整程序》 这个标题涉及到一个电力市场的建模和定价方法&#xff0c;采用了两阶段随机优化的策略&#xff0c;目标是实现电能量与深度调峰的融合。下面是对标题中各个…

逆向一个Go程序

前奏 事先声明&#xff0c;自导自演&#xff0c;纯属为了演示基本的逆向思维 用Go写一段模拟登录的代码&#xff1a; package mainimport ("fmt" )func main() {pass : ""fmt.Print("input password:")fmt.Scan(&pass)if pass "hel…

DTD(文档类型定义)

一、简介 为什么使用DTD 通过 DTD&#xff0c;您的每一个 XML 文件均可携带一个有关其自身格式的描述。 通过 DTD&#xff0c;独立的团体可一致地使用某个标准的 DTD 来交换数据。 而您的应用程序也可使用某个标准的 DTD 来验证从外部接收到的数据。 您还可以使用 DTD 来验…

【架构专题】不会业务稳定性建设架构师直接开除!!!

为什么要做业务稳定性建设&#xff1f; 首先&#xff0c;什么是业务稳定性建设&#xff1f;顾名思义&#xff0c;就是保证系统的稳定运行&#xff0c;让用户不受任何影响地使用产品。对于一个企业来说&#xff0c;这非常重要&#xff0c;因为如果产品出现问题&#xff0c;那么…

Kafka(六)消费者

目录 Kafka消费者1 配置消费者bootstrap.serversgroup.idkey.deserializervalue.deserializergroup.instance.idfetch.min.bytes1fetch.max.wait.msfetch.max.bytes57671680 (55 mebibytes)max.poll.record500max.partition.fetch.bytessession.timeout.ms45000 (45 seconds)he…

C++实现单例模式

单例模式&#xff1a; 一种设计模式&#xff0c;它的目的是确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。它适用于需要全局唯一的对象或资源的情况。 23种设计模式种最简单最常见的一种&#xff08;高频考点&#xff09; 要求&#xff1a;通过一个…

Python打印Python环境、PyTorch和CUDA版本、GPU数量名称等信息

代码&#xff1a; import torch import platformgpu_num torch.cuda.device_count() torch_version torch.__version__ python_version platform.python_version()print("Python Version: Python %s" % python_version) print("PyTorch Version: %s" %…

代码随想录刷题第四十二天| 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

代码随想录刷题第四十二天 今天是0-1背包问题&#xff0c;掌握了套路就不难了~~~ 0-1背包问题理论基础&#xff08;二维数组篇&#xff09;卡码网第46题 题目思路&#xff1a; 代码实现&#xff1a; input_line input() # 读取一行输入 mn input_line.split() m, n int…

Oracle-expdp备份变慢问题分析

问题背景&#xff1a; 应用有一个每日跑批之前的备份作业&#xff0c;通过expdp备份应用的用户数据&#xff0c;数据量大概为600G左右&#xff0c;正常情况下可以在20分钟内跑完&#xff0c;但最近expdp备份完成时间却突然猛涨到要2小时32分才能备份完&#xff0c;导致后续的跑…

QT上位机开发(会员充值软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 所有的控件当中&#xff0c;除了label、edit、radio、combobox和button之外&#xff0c;另外一个用的比较多的控件就是grid&#xff0c;也可称之为…

前台收款单选择的保险公司 提示 往来户不属于该财务组织

前台收款单选择的保险公司 提示 往来户不属于该财务组织 问题避免 新增保险公司的时候&#xff0c;找一个已经存在的保险公司&#xff0c;利用多页签复制的方式来新增 保险公司 不然不能够自动生成 财务客户

【无标题】- 自用代码 优化资源时常用的备份一个目录

右键&#xff0c;备份并删除 using System.IO; using UnityEditor; using UnityEngine; /// <summary> /// 删除字体&#xff0c;减少资源等优化功能&#xff0c;并且可以备份 /// </summary> public class BackUpYouhuaWindow : EditorWindow {[MenuItem("As…

linux 02 vmware的快照,文件管理

01.快照 使用快照&#xff1a; 同时的快照管理器&#xff1a; 如果想要返回快照&#xff0c;选择要选择的快照&#xff0c;跳转 02. 文件管理&#xff1a; cd 02.touch 2. mkdir 文件夹 mkdir -p 文件夹 &#xff08;创建之前没有的上级文件夹&#xff09;

SD-WAN:提升连锁零售企业异地组网稳定性

连锁零售企业往往拥有众多分布在不同地区的分支机构和零售店&#xff0c;为保证企业高效运转&#xff0c;各地区之间的网络连接必须稳定可靠。但基于各地网络基础设施的不同和网络延迟、带宽等限制&#xff0c;异地组网往往并不稳定。在这背景下&#xff0c;SD-WAN成为连锁零售…

CentOS:docker容器日志清理

1.先查看磁盘空间 df -h 2.找到容器的containerId-json.log文件,并清理 find /var/lib/docker/containers/ -name *-json.log |xargs du -sh 3、可以根据需求清理对应日志也可以清理数据大的日志 $ cat /dev/null > /var/lib/docker/containers/dbaee0746cc6adad3768b4ef…

[C#]使用sdcb.paddleocr部署v4版本ocr识别模型

【官方框架地址】 https://github.com/sdcb/PaddleSharp 【算法介绍】 PaddleOCR&#xff0c;全称为PaddlePaddle OCR&#xff0c;是PaddlePaddle深度学习平台下的一款强大的光学字符识别工具。它利用深度学习技术&#xff0c;实现了高精度的文字识别&#xff0c;可以帮助用户…