【音视频】 FFmpeg 解码H265

news2025/6/5 19:35:11

一、概述

实现了使用FFmpeg读取对应H265文件,并且保存为对应的yuv文件

二、实现流程

读取文件
  • 将H265/H264文件放在build路径下,然后指定输出为yuv格式

在这里插入图片描述
在这里插入图片描述

  • main函数中读取外部参数
 if (argc <= 2)
    {
        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
        exit(0);
    }
    filename    = argv[1];
    outfilename = argv[2];

  • 打开对应的输入、输出文件
  • 读取文件到缓冲区,为解码做准备
// 打开输入文件
infile = fopen(filename, "rb");
if (!infile) {
	fprintf(stderr, "Could not open %s\n", filename);
	exit(1);
}
// 打开输出文件
outfile = fopen(outfilename, "wb");
if (!outfile) {
	av_free(codec_ctx);
	exit(1);
}

// 读取文件进行解码
data      = inbuf;
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
  • 根据文件的后缀我们来判断该使用什么解码器ID
enum AVCodecID video_codec_id = AV_CODEC_ID_H265;
if(strstr(filename, "264") != NULL)
{
	video_codec_id = AV_CODEC_ID_H264;
}
else
{
	printf("default codec id:%d\n", video_codec_id);
}
  • 因为这里的文件是H264/H265,没有使用容器封装,需要使用裸流解析器

一、裸流与封装格式的区别

  1. 裸流(Bare Stream)

    • 指未经过容器封装(如 MP4、AVI、FLV)的原始编码数据,直接由编码后的 NALU 或字节流组成(如 .h264.h265 文件)。
    • 特点:数据无容器头部,仅包含编码后的视频(或音频)数据,需手动识别编码单元边界。
  2. 封装格式(Container Format)

    • 如 MP4、FLV 等,将编码数据(如 H.264 NALU)封装在容器中,包含文件头、索引、时间戳等元数据。
    • 特点:解复用器(Demuxer)可直接从容器中提取完整的 NALU 或帧数据,无需额外解析边界。

二、为什么裸流需要解析器?

裸流中编码数据是 连续存储 的,没有容器层提供的结构化信息(如 NALU 边界、帧起始位置)。例如:

  • H.264 裸流由多个 NALU 连续组成,仅通过起始码(如 00 00 01)分隔。
  • H.265 裸流类似,但起始码可能为 00 00 00 0100 00 01

解析器的作用 就是扫描裸流数据,识别这些起始码,将连续的比特流分割为独立的 NALU,确保解码器能逐个处理完整的编码单元。

  • 初始化裸流分配器
AVCodecParserContext *parser = NULL

// 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)
parser = av_parser_init(codec->id);
  • 查找解码器
// 查找解码器
codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264
  • 分配解码器上下文
// 分配codec上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
	fprintf(stderr, "Could not allocate audio codec context\n");
	exit(1);
}
  • 打开解码器,把解码器上下文的内容与解码器关联
// 将解码器和解码器上下文进行关联
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
	fprintf(stderr, "Could not open codec\n");
	exit(1);
}
解码h264/h265
  • 循环读取文件,每次从裸流解析器读取一帧数据,也就是两个startcode之间的数据
if (!decoded_frame)
{
	if (!(decoded_frame = av_frame_alloc()))
	{
		fprintf(stderr, "Could not allocate audio frame\n");
		exit(1);
	}
}

ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
					   data, data_size,
					   AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0)
{
	fprintf(stderr, "Error while parsing\n");
	exit(1);
}
data      += ret;   // 跳过已经解析的数据
data_size -= ret;   // 对应的缓存大小也做相应减小
  • 根据解码器的类型的进行不同的解析,这里主要是为了打印不同的NALU信息
  • 因为H264和H265的NAL头部不一样,因此要分开解析

对应的H264 NALU头部:

在这里插入图片描述

  • H264解码
if(video_codec_id == AV_CODEC_ID_H264) {
	if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
		printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);
	if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
		printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  pkt->data[3]&0x1f,pkt->size);
	print_h264_nal_unit_type(pkt->data, pkt->size);
}

H265头部

字段位数描述
forbidden_zero_bit1禁止位,默认 0,错误时置 1。
nal_unit_type6NALU 类型(如 VPS=32,SPS=33,PPS=34,I 帧 = 16-21 等)。
nuh_layer_id6层 ID,预留扩展位,默认 0。
nuh_temporal_id_plus13时域 ID,值为实际 ID+1。
  • H265解码
if(video_codec_id == AV_CODEC_ID_H265) {
	if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
		printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);
	if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
		printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  (pkt->data[3]&0x7e)>>1,pkt->size);
	print_h265_nal_unit_type(pkt->data, pkt->size);
}
  • 裸流解析器将H264/H265的数据帧,打包为对应的packet
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
					   data, data_size,
					   AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
  • 最后需要将对应的AVPacket送入解码器,解码出对应的AVFrame
  • AVFrame这里面的数据就是对应的yuv数据了
if (pkt->size)
	decode(codec_ctx, pkt, decoded_frame, outfile);

对应的解码函数decode如下

  • 注意内存对齐问题,需要使用linesize每行的大小,手动计算边界
  • uv分量上下都减半取样,因此取数据的时候都对应的宽高需要减半
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile)
{
    int ret;
    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if(ret == AVERROR(EAGAIN))
    {
        fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    }
    else if (ret < 0)
    {
        fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                av_get_err(ret), pkt->size);
        return;
    }

    /* read all the output frames (infile general there may be any number of them */
    while (ret >= 0)
    {
        // 对于frame, avcodec_receive_frame内部每次都先调用
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        static int s_print_format = 0;
        if(s_print_format == 0)
        {
            s_print_format = 1;
            print_video_format(frame);
        }

        // 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解
        // frame->linesize[1]  对齐的问题
        // 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]
        for(int j=0; j<frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);

        // 错误写法 用source.200kbps.766x322_10s.h264/h265测试时可以看出该种方法是错误的
        //  写入y分量
//        fwrite(frame->data[0], 1, frame->width * frame->height,  outfile);//Y
//        // 写入u分量
//        fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
//        //  写入v分量
//        fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半
    }
}
  • 打印H264/H265的NALU信息
  • 我们来看具体的打印函数print_h264_nal_unit_typeprint_h265_nal_unit_type

H264 NALU解析

  • 先找到startcode,0x00 0x00 0x00 0x01 或 0x00 0x00 0x01
  • 然后根据H264的NALU 头部解析就好了
  • 这里主要是打印出NALU的类型,比如SPS/PPS、IDR等等
void print_h264_nal_unit_type(uint8_t *data, size_t size)
{
    int i = 0;
    while (i+3 < size ) {
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {
            i += 4;
            printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);
            continue;
        }
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {
            i += 3;
            printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);
            continue;
        }
        i++;
    }
}

H264 NALU解析结果
在这里插入图片描述

  • H264 的NALU需要先传输SPS和PPS,对应type为7,8,这里还有6-SEI(图像增强信息),随后跟着一个IDR帧,表示是一个GOP的开始

  • 后面的type = 1,可能是B帧,也可能是P帧,属于非I帧的NALU类型,还需要使用slice_type来判断

在这里插入图片描述

H265 NALU解析

  • 这里的原理和H264一致,也是找到startcode,然后根据NALU头部解析
  • H265的NALU头部与H264不太一样,所以要调整
void print_h265_nal_unit_type(uint8_t *data, size_t size)
{
    int i = 0;
    while (i+3 < size ) {
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {
            i += 4;
            printf("%02x nal_type:%d, pos:%d\n", data[i],(data[i]&0x7e)>>1, i);
            continue;
        }
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {
            i += 3;
            printf("%02x nal_type:%d, pos:%d\n",data[i], (data[i]&0x7e)>>1, i);
            continue;
        }
        i++;
    }
}

H265 NALU解析结果

在这里插入图片描述

  • 解码器的 id :173,这里H265的解码器
  • 然后输出startcode是00 00 00 01
  • 起始的NALU类型分别是:32-VPS、33-SPS、34-PPS,39-SEI、20-IDR,前四个都是整个视频的配置信息,通常用于初始化和图像配置,不包含视频数据,后面的IDR是一个GOP的开始,这里开始才有视频数据

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 后续可以看到都是 0 ,1 类型的,对应B帧和P帧的类型,通常在type = 0、1的NALU传输

在这里插入图片描述

整体读取的过程

 while (data_size > 0)
    {
        if (!decoded_frame)
        {
            if (!(decoded_frame = av_frame_alloc()))
            {
                fprintf(stderr, "Could not allocate audio frame\n");
                exit(1);
            }
        }

        ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0)
        {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }
        data      += ret;   // 跳过已经解析的数据
        data_size -= ret;   // 对应的缓存大小也做相应减小

        if(video_codec_id == AV_CODEC_ID_H264) {
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
                printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
                printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  pkt->data[3]&0x1f,pkt->size);
            print_h264_nal_unit_type(pkt->data, pkt->size);
        }

        if(video_codec_id == AV_CODEC_ID_H265) {
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
                printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
                printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  (pkt->data[3]&0x7e)>>1,pkt->size);
            print_h265_nal_unit_type(pkt->data, pkt->size);
        }

        if (pkt->size)
            decode(codec_ctx, pkt, decoded_frame, outfile);



        if (data_size < VIDEO_REFILL_THRESH)    // 如果数据少了则再次读取
        {
            memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置
            data = inbuf;
            // 读取数据 长度: VIDEO_INBUF_SIZE - data_size
            len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
            if (len > 0)
                data_size += len;
        }

最后可以发现,yuv数据一样的情况,H265的压缩率比H264高的多

在这里插入图片描述

冲刷解码器
  • 最后需要传入NULL,将解码器中剩余的数据写入文件
/* 冲刷解码器 */
pkt->data = NULL;   // 让其进入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);
  • 关闭文件,释放对应内存
fclose(outfile);
fclose(infile);

avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);

free(inbuf);

整体代码

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavutil/frame.h>
#include <libavutil/mem.h>

#include <libavcodec/avcodec.h>

#define VIDEO_INBUF_SIZE (1024*1024)
#define VIDEO_REFILL_THRESH 4096




static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{
    av_strerror(errnum, err_buf, 128);
    return err_buf;
}

static void print_video_format(const AVFrame *frame)
{
    printf("width: %u\n", frame->width);
    printf("height: %u\n", frame->height);
    printf("format: %u\n", frame->format);// 格式需要注意
}

void print_h264_nal_unit_type(uint8_t *data, size_t size)
{
    int i = 0;
    while (i+3 < size ) {
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {
            i += 4;
            printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);
            continue;
        }
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {
            i += 3;
            printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);
            continue;
        }
        i++;
    }
}


void print_h265_nal_unit_type(uint8_t *data, size_t size)
{
    int i = 0;
    while (i+3 < size ) {
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {
            i += 4;
            printf("%02x nal_type:%d, pos:%d\n", data[i],(data[i]&0x7e)>>1, i);
            continue;
        }
        if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {
            i += 3;
            printf("%02x nal_type:%d, pos:%d\n",data[i], (data[i]&0x7e)>>1, i);
            continue;
        }
        i++;
    }
}

static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile)
{
    int ret;
    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if(ret == AVERROR(EAGAIN))
    {
        fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    }
    else if (ret < 0)
    {
        fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                av_get_err(ret), pkt->size);
        return;
    }

    /* read all the output frames (infile general there may be any number of them */
    while (ret >= 0)
    {
        // 对于frame, avcodec_receive_frame内部每次都先调用
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        static int s_print_format = 0;
        if(s_print_format == 0)
        {
            s_print_format = 1;
            print_video_format(frame);
        }

        // 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解
        // frame->linesize[1]  对齐的问题
        // 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]
        for(int j=0; j<frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);

        // 错误写法 用source.200kbps.766x322_10s.h264/h265测试时可以看出该种方法是错误的
        //  写入y分量
//        fwrite(frame->data[0], 1, frame->width * frame->height,  outfile);//Y
//        // 写入u分量
//        fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
//        //  写入v分量
//        fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半
    }
}
// 注册测试的时候不同分辨率的问题
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25  source.200kbps.768x320_10s.yuv
int main(int argc, char **argv)
{
    const char *outfilename;
    const char *filename;
    const AVCodec *codec;
    AVCodecContext *codec_ctx= NULL;
    AVCodecParserContext *parser = NULL;
    int len = 0;
    int ret = 0;
    FILE *infile = NULL;
    FILE *outfile = NULL;
    // AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码
    uint8_t *inbuf = malloc(VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
    uint8_t *data = NULL;
    size_t   data_size = 0;
    AVPacket *pkt = NULL;
    AVFrame *decoded_frame = NULL;

    if (argc <= 2)
    {
        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
        exit(0);
    }
    filename    = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    enum AVCodecID video_codec_id = AV_CODEC_ID_H265;
    if(strstr(filename, "264") != NULL)
    {
        video_codec_id = AV_CODEC_ID_H264;
    }
    else if(strstr(filename, "mpeg2") != NULL)
    {
        video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
    }
    else
    {
        printf("default codec id:%d\n", video_codec_id);
    }

    // 查找解码器
    codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    // 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)
    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "Parser not found\n");
        exit(1);
    }

    // 分配codec上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    // 将解码器和解码器上下文进行关联
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    // 打开输入文件
    infile = fopen(filename, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    // 打开输出文件
    outfile = fopen(outfilename, "wb");
    if (!outfile) {
        av_free(codec_ctx);
        exit(1);
    }

    // 读取文件进行解码
    data      = inbuf;
    data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);

    while (data_size > 0)
    {
        if (!decoded_frame)
        {
            if (!(decoded_frame = av_frame_alloc()))
            {
                fprintf(stderr, "Could not allocate audio frame\n");
                exit(1);
            }
        }

        ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0)
        {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }
        data      += ret;   // 跳过已经解析的数据
        data_size -= ret;   // 对应的缓存大小也做相应减小

        if(video_codec_id == AV_CODEC_ID_H264) {
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
                printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
                printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  pkt->data[3]&0x1f,pkt->size);
            print_h264_nal_unit_type(pkt->data, pkt->size);
        }

        if(video_codec_id == AV_CODEC_ID_H265) {
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )
                printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);
            if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )
                printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  (pkt->data[3]&0x7e)>>1,pkt->size);
            print_h265_nal_unit_type(pkt->data, pkt->size);
        }

        if (pkt->size)
            decode(codec_ctx, pkt, decoded_frame, outfile);



        if (data_size < VIDEO_REFILL_THRESH)    // 如果数据少了则再次读取
        {
            memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置
            data = inbuf;
            // 读取数据 长度: VIDEO_INBUF_SIZE - data_size
            len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
            if (len > 0)
                data_size += len;
        }
    }

    /* 冲刷解码器 */
    pkt->data = NULL;   // 让其进入drain mode
    pkt->size = 0;
    decode(codec_ctx, pkt, decoded_frame, outfile);

    fclose(outfile);
    fclose(infile);

    avcodec_free_context(&codec_ctx);
    av_parser_close(parser);
    av_frame_free(&decoded_frame);
    av_packet_free(&pkt);

    free(inbuf);

    printf("main finish, please enter Enter and exit\n");
    return 0;
}

更多资料:https://github.com/0voice

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试|FIT故障注入测试工具——ISO 26262合规下的智能汽车安全验证引擎

FIT&#xff08;Fault Injection Tester&#xff09;是SURESOFT专为汽车电子与工业控制设计的自动化故障注入测试工具​&#xff0c;基于ISO 26262等国际安全标准开发&#xff0c;旨在解决传统测试中效率低、成本高、安全隐患难以复现的问题&#xff0c;其核心功能包括&#xf…

3D拟合测量水杯半径

1&#xff0c;目的。 测量水杯的半径 如图所示&#xff1a; 2&#xff0c;原理。 对 3D 点云对象 进行圆柱体拟合&#xff0c;获取拟合后的半径。 3&#xff0c;注意事项。 在Halcon中使用fit_primitives_object_model_3d进行圆柱体拟合时&#xff0c;输出的primitive_para…

Python训练打卡Day38

Dataset和Dataloader类 知识点回顾&#xff1a; Dataset类的__getitem__和__len__方法&#xff08;本质是python的特殊方法&#xff09;Dataloader类minist手写数据集的了解 在遇到大规模数据集时&#xff0c;显存常常无法一次性存储所有数据&#xff0c;所以需要使用分批训练的…

Selenium基础操作方法详解

Selenium基础操作方法详解&#xff1a;从零开始编写自动化脚本&#xff08;附完整代码&#xff09; 引言 Selenium是自动化测试和网页操作的利器&#xff0c;但对于新手来说&#xff0c;掌握基础操作是成功的第一步。本文将手把手教你使用Selenium完成浏览器初始化、元素定位、…

简单三步FastAdmin 开源框架的安装

简单三步FastAdmin 开源框架的安装 第一步&#xff1a;新建站点1&#xff0c;在宝塔面板中&#xff0c;创建一个新的站点&#xff0c;并填写项目域名。 第二步&#xff1a;上传框架1&#xff0c;框架下载2&#xff0c;上传解压缩 第三步&#xff1a;配置并安装1&#xff0c;进入…

RISC-V 开发板 MUSE Pi Pro 搭建 Spacengine AI模型部署环境

视频讲解&#xff1a; RISC-V 开发板 MUSE Pi Pro 搭建 Spacengine AI模型部署环境 Spacengine 是由 进迭时空 研发的一套 AI 算法模型部署工具&#xff0c;可以方便的帮助用户部署自己的模型在端侧&#xff0c; 环境部署的方式&#xff0c;官方提供了两种方式&#xff1a; do…

【Unity】AudioSource超过MaxDistance还是能听见

unity版本&#xff1a;2022.3.51f1c1 将SpatialBlend拉到1即可 或者这里改到0 Hearing audio outside max distance - #11 by wderstine - Questions & Answers - Unity Discussions

Qt 读取和写入 INI 格式的配置文件

Qt 读取和写入 INI 格式的配置文件 前言&#xff1a;INI 配置文件在 Qt 开发中的重要性基础夯实&#xff1a;INI 文件结构与 QSettings 核心概念1. INI 文件的基本结构2. QSettings 类概述3. 初始化 QSettings 对象4. 基本读写操作5. 高级操作技巧5.1 处理数组和列表5.2 检查键…

Spring:从青铜到王者,你的Java修炼手册

一、Spring家族宇宙&#xff1a;原来你是这样的框架&#xff08;青铜段位&#xff09; 1.1 Spring的"前世今生"&#xff1a;从泡面到满汉全席 ​​2002年的泡面哲学​​&#xff1a;Rod Johnson在厨房煮泡面时突然顿悟&#xff1a;"Java开发为什么不能像泡面一…

Qt creator 设计页面控件认识与了解

记录一下 Qt 中的认识与了解&#xff1a; 在 Qt 中&#xff0c;这些功能属于 Qt Designer 的组件过滤系统&#xff0c;旨在帮助开发者在对象浏览器中快速定位和使用不同类型的控件和组件。以下是每个功能的详细讲解&#xff1a; ‌Layouts&#xff08;布局&#xff09;‌&…

NVIDIA Mellanox BlueField-2 DPU(Data Processing Unit)智能网卡的调试和使用

专有名词 OOB&#xff1a; BMC&#xff1a; BFB&#xff1a; EMMC&#xff1a; 关键词解释eMMCEmbedded Multi-Media Card——把 NAND 闪存颗粒与控制器封装在一起的板载存储件&#xff0c;类似手机里的“内置储存” .deb&#xff1a;文件是​​Debian软件包格式​​的专…

Tomcat- AJP协议文件读取/命令执行漏洞(幽灵猫复现)详细步骤

一、漏洞描述 Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器.默认情况下,Apache Tomcat会开启AJP连接器,方便与其他Web服务器通过AJP协议进行交互.但Apache Tomcat在AJP协议的实现上存在漏洞,导致攻击者可以通过发送恶意的AJP请求,可以读取或者包含Web应…

B1、进度汇报(— 25/05/31)

本文档汇总了各成员在 2025 年 5 月 11 日 ~ 5 月 31 日完成的工作。我们遇到了进度问题&#xff08;收工后需反思&#xff09;&#xff1a; 本学期第十四周&#xff08;05/19 ~ 05/25&#xff09;有相当多课程需要提交实验结果或上台展示。本学期第十六周&#xff08;06/02 ~…

Flutter实现不规则瀑布流布局拖拽重排序

因为业务&#xff0c;所以需要用flutter去实现一种不规则图形的瀑布流&#xff0c;但是同时需要支持拖拽并重新排序。效果类似如下。 查询过现有的插件&#xff0c;要么是仅支持同样大小的组件进行排序&#xff0c;要么就是动画效果不是很满意&#xff0c;有点死板&#xff0c;…

【第4章 图像与视频】4.1 图像的绘制

文章目录 前言在 Canvas 之中绘制图像drawImage() 方法的用法 前言 drawImage() 方法可以将一幅图像的整体或某个部分绘制到 canvas 内的任何位置上&#xff0c;并且允许开发者在绘制过程中对图像进行缩放。也可以将图像绘制在离屏 canvas 中&#xff0c;这样的话就可以对图像…

G25-05-31Rust开源项目日报 Top10

根据Github Trendings的统计,今日(2025-05-31统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10TypeScript项目1Pake: 利用 Rust 轻松构建轻量级多端桌面应用 创建周期:491 天开发语言:Rust协议类型:MIT LicenseStar数量:2095…

window安装nginx

步骤1&#xff1a;下载Nginx for Windows​ 访问Nginx官网下载页面&#xff1a;https://nginx.org/en/download.html 在​​Stable version​​&#xff08;稳定版&#xff09;下找到Windows版本&#xff0c;点击下载.zip文件&#xff08;如 nginx-1.28.0.zip&#xff09; 步…

ArcGIS Pro裁剪影像

方法1&#xff1a; 工具箱中&#xff0c;数据管理工具-栅格-栅格处理-裁剪栅格 注意&#xff1a;勾选上使用输入要素裁剪几何 方法2&#xff1a; 按掩膜提取工具裁剪栅格&#xff1a;在工具箱中&#xff0c;Spatial Analyst工具-提取分析-按掩膜提取

[智能算法]蚁群算法原理与TSP问题示例

目录 ​编辑 一、生物行为启发的智能优化算法 1.1 自然界的群体智能现象 1.2 人工蚁群算法核心思想 二、算法在组合优化中的应用演进 2.1 经典TSP问题建模 2.2 算法流程优化 三、TSP问题实战:Python实现与可视化 3.1 算法核心类设计 3.2 参数敏感性实验 3.3 可视化…

【Go语言】Fyne GUI 库使用指南 (面向有经验开发者)

引言 Fyne 是一个使用 Go 语言编写的、易于使用的跨平台 GUI 工具包和应用程序 API。它旨在通过单一代码库构建在桌面和移动设备上运行的应用程序。本文档面向有一定 Go 语言开发经验的开发者&#xff0c;将详细介绍 Fyne 最新版的核心功能&#xff0c;包括基础组件、布局系统…