雷神simplest_ffmpeg_player解析(一)
写在前面
学习雷神的博客,向雷神致敬~
看了雷神的小学期视频课,在Github上下载了simplest_ffmpeg_player的代码,为代码加上了注释,作为留存。
2019.07.16
视频中的前置知识点
simple_ffmpeg_decoder.cpp注释
simple_ffmpeg_decoder_pure.cpp注释
链接及参考资料
《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频
FFmpeg Documentation
FFmpeg源代码简单分析
知识点
封装、编码格式
FFmpeg解码流程及数据结构
FFmpeg数据结构简介
AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
AVInputFormat:每种封装格式对应一个该结构体
AVStream:视频文件每个视频(音频)流对应一个该结构体
AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodec:每种视频(音频)编解码器对应一个该结构体
AVPacket:存储一帧压缩编码数据
AVFrame:存储一帧解码后像素(采样)数据
AVFormatContext
- iformat:输入视频的AVIputFormat
- nb_streams:输入视频的AVStream个数
- streams:输入视频的AVStream[]数组
- duration:输入视频的时长(以微秒为单位)
- bit_rate:输入视频的码率
AVIputFormat
- name:封装格式名称
- long_name:封装格式的长名称
- extensions:封装格式的扩展名
- id:封装格式ID
- 一些封装格式处理的接口函数
AVStream
- id:序号
- codec:该流对应的AVCodecContext
- time_base:该流的时基
- r_frame_rate:该流的帧率
AVCodecContext
- codec:编解码器的AVCodec
- width,height:图像的宽高(只针对视频)
- pix_fmt:像素格式(只针对视频)
- sample_rate:采样率(只针对音频)
- channels:声道数(只针对音频)
- sample_fmt:采样格式(只针对音频)
AVCodec
- name:编解码器名称
- long_name:编解码器长名称
- type:编解码器类型
- id:编解码器ID
- 一些编解码的接口函数
1.simplest_ffmpeg_decoder.cpp
/*** 最简单的基于FFmpeg的视频解码器* Simplest FFmpeg Decoder** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序实现了视频文件解码为YUV数据。它使用了libavcodec和* libavformat。是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* This software is a simplest decoder based on FFmpeg.* It decodes video to YUV pixel data.* It uses libavcodec and libavformat.* Suitable for beginner of FFmpeg.**/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif/*** 将视频解封装、解码,转换为yuv格式**/
int main(int argc, char* argv[])
{// 封装格式上下文的结构体,也是统领全局的结构体,保存了视频文件封装格式的相关信息AVFormatContext *pFormatCtx;// 视频流在文件中的位置int i, videoindex;// 编码器上下文结构体,保存了视频(音频)编解码相关信息AVCodecContext *pCodecCtx;// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体AVCodec *pCodec;// 存储一帧解码后像素(采样)数据AVFrame *pFrame,*pFrameYUV;//unsigned char *out_buffer;// 存储一帧压缩编码数据AVPacket *packet;// width×height,用于计算YUV数据分布int y_size;// 视频是否解码成功的返回int ret, got_picture;// libswsscale 上下文struct SwsContext *img_convert_ctx;// 输入文件char filepath[]="Titanic.mkv";// 输出文件FILE *fp_yuv=fopen("output.yuv","wb+"); // 注册复用器,编码器等(参考FFmpeg解码流程图)av_register_all();avformat_network_init();pFormatCtx = avformat_alloc_context();// 打开多媒体数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){printf("Couldn't open input stream.\n");return -1;}// 读取一部分视音频数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_find_stream_info(pFormatCtx,NULL)<0){printf("Couldn't find stream information.\n");return -1;}// 每个视频文件中有多个流(视频流、音频流、字幕流等,而且可有多个),循环遍历找到视频流// 判断方式:AVFormatContext->AVStream->AVCodecContext->codec_type是否为AVMEDIA_TYPE_VIDEOvideoindex=-1;for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){videoindex=i;break;}// 如果没有视频流,返回if(videoindex==-1){printf("Didn't find a video stream.\n");return -1;}// 保存视频流中的AVCodecContextpCodecCtx=pFormatCtx->streams[videoindex]->codec;// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL){printf("Codec not found.\n");return -1;}// (参考FFmpeg解码流程图)if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){printf("Could not open codec.\n");return -1;}// 创建一个AVFrame,用来存放解码后的一帧的数据pFrame=av_frame_alloc();pFrameYUV=av_frame_alloc();// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1));// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);// 创建一个AVPacket,用来存放下面循环获取到的未解码帧packet=(AVPacket *)av_malloc(sizeof(AVPacket));//Output Info-----------------------------printf("--------------- File Information ----------------\n");av_dump_format(pFormatCtx,0,filepath,0);printf("-------------------------------------------------\n");// sws_getContext():初始化一个SwsContextimg_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); // 循环读取帧数据while(av_read_frame(pFormatCtx, packet)>=0){// 取出视频流,if(packet->stream_index==videoindex){// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if(ret < 0){printf("Decode Error.\n");return -1;}if(got_picture){// sws_scale():处理图像数据,用于转换像素sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);// 根据YUV数据格式,分离Y、U、V数据// 如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据// 其中前w*h Byte存储Y,接着的w*h*1/4 Byte存储U,最后w*h*1/4 Byte存储Vy_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //Ufwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //Vprintf("Succeed to decode 1 frame!\n");}}av_free_packet(packet);}//flush decoder//FIX: Flush Frames remained in Codecwhile (1) {// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if (ret < 0)break;if (!got_picture)break;// // sws_scale():处理图像数据,用于转换像素sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);int y_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //Ufwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //Vprintf("Flush Decoder: Succeed to decode 1 frame!\n");}// sws_freeContext():释放一个SwsContextsws_freeContext(img_convert_ctx);// close and freefclose(fp_yuv);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0;
}
2.simplest_ffmpeg_decoder_pure
/*** 最简单的基于FFmpeg的视频解码器(纯净版)* Simplest FFmpeg Decoder Pure** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序实现了视频码流(支持HEVC,H.264,MPEG2等)解码为YUV数据。* 它仅仅使用了libavcodec(而没有使用libavformat)。* 是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* This software is a simplest decoder based on FFmpeg.* It decode bitstreams to YUV pixel data.* It just use libavcodec (do not contains libavformat).* Suitable for beginner of FFmpeg.*/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
};
#endif
#endif//test different codec
#define TEST_H264 1
#define TEST_HEVC 0int main(int argc, char* argv[])
{// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体AVCodec *pCodec;// 编码器上下文结构体,保存了视频(音频)编解码相关信息AVCodecContext *pCodecCtx= NULL;// 保存了当前帧的信息,包括offset、dts、pts、宽高等AVCodecParserContext *pCodecParserCtx=NULL;FILE *fp_in;FILE *fp_out;// 存储一帧解码后像素(采样)数据AVFrame *pFrame;const int in_buffer_size=4096;// FF_INPUT_BUFFER_PADDING_SIZE:在输入比特流的末尾用于解码的额外分配字节的所需数量。// 这主要是因为一些优化的比特流读取器一次读取32位或64位并且可以读取结束。// 注意:如果附加字节的前23位不为0,则损坏的MPEG比特流可能导致过度读取和段错误。unsigned char in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};unsigned char *cur_ptr;int cur_size;// 存储一帧压缩编码数据AVPacket packet;// 视频是否解码成功的返回int ret, got_picture;// hevc h264 m2v -> yuv
#if TEST_HEVCenum AVCodecID codec_id=AV_CODEC_ID_HEVC;char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264AVCodecID codec_id=AV_CODEC_ID_H264;char filepath_in[]="bigbuckbunny_480x272.h264";
#elseAVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;char filepath_in[]="bigbuckbunny_480x272.m2v";
#endifchar filepath_out[]="bigbuckbunny_480x272.yuv";int first_time=1;//av_log_set_level(AV_LOG_DEBUG);// 注册复用器,编码器等(参考FFmpeg解码流程图)avcodec_register_all();// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)pCodec = avcodec_find_decoder(codec_id);if (!pCodec) {printf("Codec not found\n");return -1;}// 创建AVCodecContext结构体pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("Could not allocate video codec context\n");return -1;}// 初始化AVCodecParserContext。其参数是codec_id,所以同时只能解析一种// AVCodecParser用于解析输入的数据流并把它们分成一帧一帧的压缩编码数据。// 比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。// 核心函数是av_parser_parse2()pCodecParserCtx=av_parser_init(codec_id);if (!pCodecParserCtx){printf("Could not allocate video parser context\n");return -1;}//if(pCodec->capabilities&CODEC_CAP_TRUNCATED)// pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; // 使用给定的AVCodec初始化AVCodecContext;// 在使用这个函数之前需要使用avcodec_alloc_context3()分配的contextif (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {printf("Could not open codec\n");return -1;}//Input Filefp_in = fopen(filepath_in, "rb");if (!fp_in) {printf("Could not open input stream\n");return -1;}//Output Filefp_out = fopen(filepath_out, "wb");if (!fp_out) {printf("Could not open output YUV file\n");return -1;}pFrame = av_frame_alloc();// 把packet的参数设为默认值,要求packet的内存已经分配好了av_init_packet(&packet);while (1) {// 获取视频文件的总长度cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);if (cur_size == 0)break;cur_ptr=in_buffer;while (cur_size>0){/*** 解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据* Parse a packet.** @param s parser context.* @param avctx codec context.* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.* @param buf input buffer.* @param buf_size input length, to signal EOF, this should be 0 (so that the last frame can be output).* @param pts input presentation timestamp.* @param dts input decoding timestamp.* @param pos input byte position in stream.* @return the number of bytes of the input bitstream used.** Example:* @code* while(in_len){* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,* in_data, in_len,* pts, dts, pos);* in_data += len;* in_len -= len;** if(size)* decode_frame(data, size);* }* @endcode** 其中poutbuf指向解析后输出的压缩编码数据帧,buf指向输入的压缩编码数据。* 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。* 当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理。*/int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,&packet.data, &packet.size,cur_ptr , cur_size ,AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);cur_ptr += len;cur_size -= len;// 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。if(packet.size==0)continue;//Some Info from AVCodecParserContextprintf("[Packet]Size:%6d\t",packet.size);switch(pCodecParserCtx->pict_type){case AV_PICTURE_TYPE_I: printf("Type:I\t");break;case AV_PICTURE_TYPE_P: printf("Type:P\t");break;case AV_PICTURE_TYPE_B: printf("Type:B\t");break;default: printf("Type:Other\t");break;}printf("Number:%4d\n",pCodecParserCtx->output_picture_number);// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (got_picture) {if(first_time){printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);first_time=0;}//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Succeed to decode 1 frame!\n");}}}//Flush Decoderpacket.data = NULL;packet.size = 0;while(1){// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (!got_picture){break;}else {//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Flush Decoder: Succeed to decode 1 frame!\n");}}// close and freefclose(fp_in);fclose(fp_out);av_parser_close(pCodecParserCtx);av_frame_free(&pFrame);avcodec_close(pCodecCtx);av_free(pCodecCtx);return 0;
}
雷神simplest_ffmpeg_player解析(一)相关推荐
- 音视频编解码 -- 编码参数 CRF
之前多多少少接触过一些编解码参数,CRF 参数也用过,但是最近在和朋友们聊天时,说到使用 FFMPEG 过程中碰到 CRF 参数,以及具体作用流程,这个之前一直没有跟踪过,也没有详细记录过,所以吊起了 ...
- SpringBoot入门-源码解析(雷神)
一.Spring Boot入门 视频学习资料(雷神): https://www.bilibili.com/video/BV19K4y1L7MT?p=1 github: https://github.c ...
- 音视频学习笔记(雷神)—技术解析
音视频技术解析 封装技术+视频压缩编解码+音频压缩编解码 这是技术层 流媒体传输协议 这是网络层 视频播放器解析 解协议 从视频播放器的角度做解析,拿到传输而来的视频数据后,首先要解协议(传输协议) ...
- 【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我
一 前言 最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大.那写这 ...
- 雷神3开方算法解析,游戏运行速度提高四倍。
我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然 ...
- 音视频 FLV格式解析
前言 本文介绍FLV封装格式,直播场景下拉流比较常见的是http-flv直播流,具有延时低.易传输等特点. 格式概览 总体上看,FLV包括文件头(File Header)和文件体(File Body) ...
- SpringBoot 自动装配原理解析
自动装配是 Spring Boot 的核心部分,也是 Spring Boot 功能的基础,正是由于自动装配,才 将我们从 Bean 的繁复配置中解脱出来.那么 Spring Boot 中的自动装配指的 ...
- 雷神轮胎携手JBL 演绎科技降噪、感受非凡音悦
近日,雷神轮胎与哈曼旗下知名音响品牌JBL进行合作,携手演绎科技降噪,致力于为广大车主带来更舒适愉悦的驾乘体验. 雷神轮胎作为一个中高端黑科技静音轮胎品牌,此次与全球久负盛名的专业扬声器生产商JBL强 ...
- UDP-RTP协议解析
一.RTP协议 数据传输协议RTP,用于实时传输数据.RTP报文由两部分组成:报头和有效载荷 二.RTP的会话过程 当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址.目的传输地址由一个网 ...
- 音视频学习-h264裸流的解析
h264的解析代码,来自雷神的博客 //============================================================================ // ...
最新文章
- Windows下Wireshark安装版本选择方式
- FLEX SharedObject介绍及应用
- java 获取麦克_Java Sound API-捕获麦克风
- Linux下创建用户、切换用户、删除用户
- Bzoj 2127 happiness 最小割
- 有关findviewbyid 一个错误用法
- Spring Security(四) —— 核心过滤器源码分析
- NEC协议——红外遥控的使用
- 进程间通信(匿名管道、命名管道、共享内存)
- HBase的java操作,最新API。(查询指定行、列、插入数据等)
- 数据迁移软件|如何将旧电脑的数据传输到新电脑?
- AI创作教程之 Stable Diffusion 为何是人工智能新时代艺术创作的基石
- 电脑快捷方式变白原因及解决方法——血的教训呜呜呜
- 冰点还原忘记密码如何修改配置或卸载
- 深入理解计算机系统bomb实验
- 中山大学计算机软件专业,【广州日报】中山大学在珠海校区新成立人工智能学院和软件工程学院...
- 计算机课情感态度与价值观,浅谈信息技术课中情感态度价值观的培养
- 《A brief review of image denoising algorithms and beyond》
- 华为云服务器使用之搭建极简服务器
- 机器学习入门(二) 准备工作
热门文章
- bilibili 弹幕协议分析,golang 还原代码
- codevs 1024 一塔湖图 floyd 解题报告
- 单片机常用芯片总结(一)——LCD1602液晶屏
- 东西帝国时代:西半球的罗马帝国和东半球的秦汉帝国
- Qt 弹出对话框选择图片并显示
- 姿态估计1-06:FSA-Net(头部姿态估算)-源码无死角讲解(1)-训练代码总览
- 腾讯与阿里巴巴投资可编程芯片公司Barefoot Networks
- 奥鹏计算机基础18秋在线作业答案,1056《 计算机基础》20秋西南大学在线作业答案答案...
- 学习推荐!吴恩达 AI 课程及学习路线最全梳理
- 心形函数的正确打开方式(Unity3D Shader)