FFMPEG+SDL2.0流媒体开发3---简易MP4视频播放器,提取MP4的H264视频序列解码并且显示

简介

之前写了一遍提取MP4中的音视频并且解码,这一篇引入SDL2.0来显示解码后的视频序列 实现一个简易的 视频播放器。

我这里用的FFMPEG和SDL2.0都是最新版的 可能网上的资料不是很多,API接口也变了很多,不过大体的思路还是一样的。

分析几个FFMPEG函数

在这之前我们分析几个代码中可能引起疑问的FFMPEG几个函数的源代码,我已经尽我的能力添加了注释,因为实在没有文档可能有的地方也不是很详尽  不过大体还是能看懂的

av_image_alloc (分配图片缓冲区)

我们在FFMPEG中引用了此函数,下面列举的函数都是这个函数里所引用到的 我都 添加了注释  这里注意下面的

pointers 参数是一个指针数组  实际上他在初始化完毕之后会被赋值成连续的内存序列 具体看源代码
int av_image_alloc(uint8_t *pointers[4], int linesizes[4],
                   int w, int h, enum AVPixelFormat pix_fmt, int align)
{
     //获取描述符
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
	//
    int i, ret;
    uint8_t *buf;
    //如果不存在描述符那么返回错误
    if (!desc)
        return AVERROR(EINVAL);
     //检测图像宽度 高度
    if ((ret = av_image_check_size(w, h, 0, NULL)) < 0)
        return ret;
    //填充line sizes
    if ((ret = av_image_fill_linesizes(linesizes, pix_fmt, align>7 ? FFALIGN(w, 8) : w)) < 0)
        return ret;

	 //初始化0
    for (i = 0; i < 4; i++)
        linesizes[i] = FFALIGN(linesizes[i], align);
    //如果计算的缓冲区尺寸<0
    if ((ret = av_image_fill_pointers(pointers, pix_fmt, h, NULL, linesizes)) < 0)
        return ret;
    //如果失败 重新分配buf
    buf = av_malloc(ret + align);
    if (!buf)
        return AVERROR(ENOMEM);
		//再次调用 分配连续缓冲区  赋值给 pointers
    if ((ret = av_image_fill_pointers(pointers, pix_fmt, h, buf, linesizes)) < 0) {
	    //如果分配失败那么释放 缓冲区
        av_free(buf);
        return ret;
    }
	//检测像素描述符 AV_PIX_FMT_FLAG_PAL 或AV_PIX_FMT_FLAG_PSEUDOPAL
	//Pixel format has a palette in data[1], values are indexes in this palette.
	/**
		The pixel format is "pseudo-paletted". This means that FFmpeg treats it as
		* paletted internally, but the palette is generated by the decoder and is not
		* stored in the file. *
	*/
    if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL)
	     //设置系统调色板
        avpriv_set_systematic_pal2((uint32_t*)pointers[1], pix_fmt);

    return ret;
}

avpriv_set_systematic_pal2(设置系统调色板)

//设置系统化调色板根据不同像素格式

int avpriv_set_systematic_pal2(uint32_t pal[256], enum AVPixelFormat pix_fmt)
{
    int i;

    for (i = 0; i < 256; i++) {
        int r, g, b;

        switch (pix_fmt) {
        case AV_PIX_FMT_RGB8:
            r = (i>>5    )*36;
            g = ((i>>2)&7)*36;
            b = (i&3     )*85;
            break;
        case AV_PIX_FMT_BGR8:
            b = (i>>6    )*85;
            g = ((i>>3)&7)*36;
            r = (i&7     )*36;
            break;
        case AV_PIX_FMT_RGB4_BYTE:
            r = (i>>3    )*255;
            g = ((i>>1)&3)*85;
            b = (i&1     )*255;
            break;
        case AV_PIX_FMT_BGR4_BYTE:
            b = (i>>3    )*255;
            g = ((i>>1)&3)*85;
            r = (i&1     )*255;
            break;
        case AV_PIX_FMT_GRAY8:
            r = b = g = i;
            break;
        default:
            return AVERROR(EINVAL);
        }
        pal[i] = b + (g << 8) + (r << 16) + (0xFFU << 24);
    }

    return 0;
}

av_image_fill_pointers(填充av_image_alloc传递的unsigned char** data和linesize)

//返回图像所需的大小
//并且分配了连续缓冲区  将 data 拼接成一个内存连续的 序列
int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,
                           uint8_t *ptr, const int linesizes[4])
{
    int i, total_size, size[4] = { 0 }, has_plane[4] = { 0 };
    //获取描述符
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
	//清空指针数组
    memset(data  , 0, sizeof(data[0])*4);
   //如果不存在描述符 返回错误
    if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
        return AVERROR(EINVAL);
    //data[0]初始化为ptr
    data[0] = ptr;
	//如果每行的像素 大于INT类型最大值 -1024/高度 返回
    if (linesizes[0] > (INT_MAX - 1024) / height)
        return AVERROR(EINVAL);
    //初始化size[0]
    size[0] = linesizes[0] * height;
    //如果 描述符的标志是AV_PIX_FMT_FLAG_PAL或者AV_PIX_FMT_FLAG_PSEUDOPAL 那么表明调色板放在data[1]并且是 256 32位置
    if (desc->flags & AV_PIX_FMT_FLAG_PAL ||
        desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL)
	{
        size[0] = (size[0] + 3) & ~3;
        data[1] = ptr + size[0];
        return size[0] + 256 * 4;
    }
     /**
     * Parameters that describe how pixels are packed.
     * If the format has 2 or 4 components, then alpha is last.
     * If the format has 1 or 2 components, then luma is 0.
     * If the format has 3 or 4 components,
     * if the RGB flag is set then 0 is red, 1 is green and 2 is blue;
     * otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V. 

     */
    for (i = 0; i < 4; i++)
        has_plane[desc->comp[i].plane] = 1;
	//下面是计算总的需要的缓冲区大小
    total_size = size[0];
    for (i = 1; i < 4 && has_plane[i]; i++) {
        int h, s = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
        data[i] = data[i-1] + size[i-1];
        h = (height + (1 << s) - 1) >> s;
        if (linesizes[i] > INT_MAX / h)
            return AVERROR(EINVAL);
        size[i] = h * linesizes[i];
        if (total_size > INT_MAX - size[i])
            return AVERROR(EINVAL);
        total_size += size[i];
    }
   //返回总的缓冲区 大小
    return total_size;
}

av_image_fill_linesizes(填充行线宽)

//填充LineSize数组 ,linesize代表每一刚的线宽 像素为单位
int av_image_fill_linesizes(int linesizes[4], enum AVPixelFormat pix_fmt, int width)
{
    int i, ret;
	//获取格式描述符
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
    int max_step     [4];       /* max pixel step for each plane */
    int max_step_comp[4];       /* the component for each plane which has the max pixel step */
    //初始化指针数组 0
    memset(linesizes, 0, 4*sizeof(linesizes[0]));
    //如果不存在那么返回错误
    if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
        return AVERROR(EINVAL);
    //下面的代码都是填充线宽的代码
    av_image_fill_max_pixsteps(max_step, max_step_comp, desc);
    for (i = 0; i < 4; i++) {
        if ((ret = image_get_linesize(width, i, max_step[i], max_step_comp[i], desc)) < 0)
            return ret;
        linesizes[i] = ret;
    }

    return 0;
}

例子 提取MP4文件的视频,并播放实现简易视频播放器

#include "stdafx.h"
/************************************************************************/
/* 利用分流器分流MP4文件音视频并进行解码输出
Programmer小卫-USher 2014/12/17
/************************************************************************/
//打开
#define __STDC_FORMAT_MACROS
#ifdef _CPPRTTI
extern "C"
{
#endif
#include "libavutil/imgutils.h"    //图像工具
#include "libavutil/samplefmt.h"  // 音频样本格式
#include "libavutil/timestamp.h"  //时间戳工具可以 被用于调试和日志目的
#include "libavformat/avformat.h" //Main libavformat public API header  包含了libavf I/O和   Demuxing  和Muxing 库
#include "SDL.h"
#ifdef _CPPRTTI
};
#endif

//音视频编码器上下文
static AVCodecContext *pVideoContext,*pAudioContext;
static FILE *fVideoFile,*fAudioFile;  //输出文件句柄
static AVStream *pStreamVideo,*pStreamAudio; //媒体流
static unsigned char * videoDstData[4];  //视频数据
static int videoLineSize[4]; //
static int videoBufferSize; //视频缓冲区大小
static AVFormatContext *pFormatCtx=NULL; //格式上下文
static AVFrame*pFrame=NULL ; //
static AVPacket pkt;  //解码媒体包
static int ret=0; //状态
static int gotFrame; //获取到的视频流
//音视频流的索引
static int videoStreamIndex,audioStreamIndex;
//解码媒体包
//SDL定义
SDL_Window * pWindow = NULL;
SDL_Renderer *pRender = NULL;
SDL_Texture *pTexture = NULL;
SDL_Rect dstrect = {0,0,800,600};
int frame = 0;
int indexFrameVideo=0;
static int decode_packet(int* gotFrame, int param2)
{
	int ret  = 0 ;
	//解码数据大小
	int decodedSize=pkt.size ;
	//初始化获取的数据帧为0
	*gotFrame=0;
	//如果是视频流那么 解包视频流
	if(pkt.stream_index==videoStreamIndex)
	{  

		//解码数据到视频帧
		if((ret=avcodec_decode_video2(pVideoContext,pFrame,gotFrame,&pkt))<0)
		{
			//解码视频帧失败
			return ret ;
		}
		indexFrameVideo++;
		//copy 解压后的数据到我们分配的空间中
		if(*gotFrame)
		{
			//拷贝数据
			av_image_copy(videoDstData,videoLineSize, (const uint8_t **)(pFrame->data), pFrame->linesize,pVideoContext->pix_fmt, pVideoContext->width, pVideoContext->height);
			//写入数据到缓冲区
			//fwrite(videoDstData[0], 1, videoBufferSize, fVideoFile);
		    printf("输出当前第%d帧,大小:%d\n",indexFrameVideo,videoBufferSize);
    	int n = SDL_BYTESPERPIXEL(pStreamVideo->codec->pix_fmt);

		//更新纹理

		SDL_UpdateTexture(pTexture, &dstrect, (const void*)videoDstData[0], videoLineSize[0]);

		//拷贝纹理到2D模块
		SDL_RenderCopy(pRender, pTexture,NULL, &dstrect);
		//延时 1000ms*1/25
		SDL_Delay(1000 * 1 / frame);
		//显示Render渲染曾
		SDL_RenderPresent(pRender);
		}else
		{
			printf("第%d帧,丢失\n",indexFrameVideo);
		}
	}
	//音频不管
	else if(pkt.stream_index==audioStreamIndex)
	{
		///解码音频信息
// 		if ((ret = avcodec_decode_audio4(pAudioContext, pFrame, gotFrame, &pkt)) < 0)
// 			return ret;
// 		decodedSize = FFMIN(ret, pkt.size);
// 		//算出当前帧的大小
// 		size_t unpadded_linesize = pFrame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)pFrame->format);
// 		///写入数据到音频文件
// 		fwrite(pFrame->extended_data[0], 1, unpadded_linesize, fAudioFile);
	}
	//取消所有引用  并且重置frame字段
	av_frame_unref(pFrame);
	return decodedSize ;
}

	int Demuxing(int argc, char** argv)
	{
		if (argc < 4)
		{
			printf("Parameter Error!\n");
			return 0;
		}

		//注册所有混流器 过滤器
		av_register_all();
		//注册所有编码器
		avcodec_register_all();
		//媒体输入源头
		char*pInputFile = argv[1];
		//视频输出文件
		char*pOutputVideoFile = argv[3];
		//音频输出文件
		char*pOutputAudioFile = argv[2];
		//分配环境上下文
		pFormatCtx = avformat_alloc_context();
		//打开输入源  并且读取输入源的头部
		if (avformat_open_input(&pFormatCtx, pInputFile, NULL, NULL) < 0)
		{
			printf("Open Input Error!\n");
			return 0;
		}
		//获取流媒体信息
		if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
		{
			printf("获取流媒体信息失败!\n");
			return 0;
		}
		//打印媒体信息
		av_dump_format(pFormatCtx, 0, pInputFile, 0);
		for (unsigned i = 0; i < pFormatCtx->nb_streams; i++)
		{
			AVStream *pStream = pFormatCtx->streams[i];
			AVMediaType mediaType = pStream->codec->codec_type;
			//提取不同的编解码器
			if (mediaType == AVMEDIA_TYPE_VIDEO)
			{
				videoStreamIndex = i;
				pVideoContext = pStream->codec;
				pStreamVideo = pStream;
				fVideoFile = fopen(pOutputVideoFile, "wb");
				frame = pVideoContext->framerate.num;
				if (!fVideoFile)
				{
					printf("con't open file!\n");
					goto end;
				}
				//计算解码后一帧图像的大小
				//int nsize = avpicture_get_size(PIX_FMT_YUV420P, 1280, 720);
				//分配计算初始化 图像缓冲区 调色板数据
				int ret = av_image_alloc(videoDstData, videoLineSize, pVideoContext->width, pVideoContext->height, pVideoContext->pix_fmt, 1);
				if (ret < 0)
				{
					printf("Alloc video buffer error!\n");
					goto end;
				}
				//avpicture_fill((AVPicture *)pFrame, videoDstData[0], PIX_FMT_YUV420P, 1280, 720);
				videoBufferSize = ret;
			}
			else if (mediaType == AVMEDIA_TYPE_AUDIO)
			{
				audioStreamIndex = i;
				pAudioContext = pStream->codec;
				pStreamAudio = pStream;
				fAudioFile = fopen(pOutputAudioFile, "wb");
				if (!fAudioFile)
				{
					printf("con't open file!\n");
					goto end;
				}
				//分配视频帧
				pFrame = av_frame_alloc();
				if (pFrame == NULL)
				{
					av_freep(&videoDstData[0]);
					printf("alloc audio frame error\n");
					goto end;
				}
			}
			AVCodec *dec;
			//根据编码器id查找编码器
			dec = avcodec_find_decoder(pStream->codec->codec_id);
			if (dec == NULL)
			{
				printf("查找编码器失败!\n");
				goto end;
			}
			if (avcodec_open2(pStream->codec, dec, nullptr) != 0)
			{
				printf("打开编码器失败!\n");
				goto end;
			}

		}
		av_init_packet(&pkt);
		pkt.data = NULL;
		pkt.size = 0;

		//读取媒体数据包  数据要大于等于0
		while (av_read_frame(pFormatCtx, &pkt) >= 0)
		{
			AVPacket oriPkt = pkt;
			do
			{
				//返回每个包解码的数据
				ret = decode_packet(&gotFrame, 0);
				if (ret < 0)
					break;
				//指针后移  空闲内存减少
				pkt.data += ret;
				pkt.size -= ret;
				//
			} while (pkt.size > 0);
			//释放之前分配的空间  读取完毕必须释放包
			av_free_packet(&oriPkt);
		}

	end:
		//关闭视频编码器
		avcodec_close(pVideoContext);
		//关闭音频编码器
		avcodec_close(pAudioContext);
		avformat_close_input(&pFormatCtx);
		fclose(fVideoFile);
		fclose(fAudioFile);
		//释放编码帧
		avcodec_free_frame(&pFrame);
		//释放视频数据区
		av_free(videoDstData[0]);
		return 0;
	}

	int _tmain(int argc, char*argv[])
	{
		SDL_Init(SDL_INIT_VIDEO);
		//创建窗口
		pWindow = SDL_CreateWindow("YUV420P", 200, 100, 800, 600, 0);
		//启用硬件加速
		pRender=SDL_CreateRenderer(pWindow, -1, 0);
		dstrect.x = 0;
		dstrect.y = 0;
		dstrect.w = 1280;
		dstrect.h = 720;
		//创建一个纹理  设置可以Lock  YUV420P 格式 1280*720
		pTexture = SDL_CreateTexture(pRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, 1280, 720);
		Demuxing(argc, argv);
		//释放
		SDL_RenderClear(pRender);
		SDL_DestroyTexture(pTexture);
		SDL_DestroyRenderer(pRender);
		SDL_DestroyWindow(pWindow);
		SDL_Quit();
		return  0;
	}

代码运行界面

时间: 2024-09-20 10:15:41

FFMPEG+SDL2.0流媒体开发3---简易MP4视频播放器,提取MP4的H264视频序列解码并且显示的相关文章

c-在用ffmpeg 2.0做一个转码工具的时候,转出的视频 视频播放速度变快

问题描述 在用ffmpeg 2.0做一个转码工具的时候,转出的视频 视频播放速度变快 在用最新版 ffmpeg 2.0 转换视频的时候 首先遇到的一个问题是 在 avformat_write_header(pFormatCtxOut NULL); 处报错:[mp4 @ 0x8183600] track 1: codec frame size is not set 虽然报了这个错误 但是还是能够转码,只是转出来的视频本来30秒 但是15秒左右就没有了,应该没有丢帧,只不过画面播放得特别快,就像快进

Bino v0.9.0发布 一个Linux下的视频播放器

Bino 是一个Linux下的10244.html">视频播放器,支持 3D 视频,支持大量的视频格式,支持多显示器. Bino is a video player with two special features: ·Support for 3D videos, with a wide variety of input and output formats. ·Support for multi-display video, e.g. for powerwalls, Virtual R

基于FFMPEG SDK流媒体开发1---解码媒体文件流信息

最近项目涉及到流媒体等开发,由于有过开发经验深知其难度所在,没办法只能重新拾起,最新版的SDK被改的一塌糊涂,不过大体的开发思路都是一样的,看多少书查多少资料都无用,一步一步的编写代码 才是学好的关键.. 我会把每一天的学习经过,更新到博文上,希望能给更多想学习的人带来帮助,篇尾附上工程     以及最新版本SDK. FFMPEG被大多数的人命令行来使用,其实在真正的流媒体开发中,要想灵活运用其开发流媒体应用层序,必须使用官方SDK开发  ,实际上我们市面上好多产品 都是基于FFMPEG,比如

FLEX4.0开发流媒体视频播放器

视频播放器这个做开发的兄弟们应该都熟悉,现在的视听网站这么火热,流媒体技术也相当成熟,网上的介绍也很多.不过基本上流媒体播放器都是flash,大多是写AactionScript脚本开发,利用FLEX开发也比较方便,和VS.NET的开发环境一样,看起来也熟悉,开发起来也有感觉些,我想至少应该比写AS脚本要有感觉些,当然FLEX开发的应用程序最终也会被解析成AS脚本,生成SWF文件,供WEB页面嵌入调用.最近稍微空闲那么一点,从同事那拷了个FLEX4.0,装上感觉下,之前也有意想做个流媒体播放器,正

php开发的简易扫雷游戏

我觉得用PHP+JS可以设计出挺牛逼的二维网页游戏,当然网络型的网页游戏后台数据的交互可以使用java来做数据处理. 开发的简易扫雷游戏-扫雷游戏开发">   <?php     $init = $_POST["init"];//game restart   $clickvalue = $_POST["clickvalue"];//minesweeping   $checkflag = 0;//Victory or defeat   $clic

ASP开发WAP简易邮件系统实例

下一篇:ASP开发WAP简易邮件系统实例(续) 随着手机用户的不断增加,WAP站点如雨后春笋迅速的滋长开来,手机邮箱也不断的出现在人的眼前,笔者也曾经开发了一套手机邮箱的系统,但由于时间仓促再加上后来一直忙于工作事情,系统功能也就再未加强,今日有幸借助于IT168原创网公开出来,与大家共同分享,希望大家可以借题发挥,加强系统功能,能够更好的应用于实际. 测试软件:Opera M3gate 测试机型:Eg730+ Nokia 6681 Moto V3i 邮件组件:W3 Jmail 4.4 一.WM

ASP开发WAP简易邮件系统实例(续)

上一篇:ASP开发WAP简易邮件系统实例 3) 发送邮件 图五 发送邮件功能的实现主要采用了Jmail.Message对象完成,关于所要用到的对象相关知识在前面已经叙述过了,大家可以将上面的功能介绍与源码对照起来阅读,这样有助于理解整个邮件发送过程,当然如果将WM元素换为HTMLF元素,这样一个简单的WEB邮件发送功能也就完成了,send.asp为邮件发送页面 sendok.asp为邮件发送处理页面 Send.asp 〈% @LANGUAGE="VBSCRIPT" CODEPAGE=&

Android应用开发之简易、大气音乐播放器实现专辑倒影效果_Android

今天要实现的功能是实现专辑倒影效果,这个功能已经属于图像处理方面的了,对图像处理我不怎么在行,等一下会介绍一个很实用的工具类,专门用来进行图像处理的.这个工具类不是我写的,我只是拿来用到自己的项目当中,这已经足够了,我已经完美实现我想要的效果. 效果图: < 一个很有用的工具类 /SimpleBeautyMusicPlayer/src/com/wwj/sb/utils/ImageUtil.java package com.wwj.sb.utils; import java.io.ByteArra

SDL2.0 学习笔记-1 windows下的第一个测试程序

SDL全称是Simple DirectMedia Layer,是一个开源的.跨平台(win32,linux,mac)的多媒体开发c语言库. 官方网站 http://www.libsdl.org/   第一步,下载源文件 首先去这个页面http://www.libsdl.org/tmp/download-2.0.php下载相关资源文件,直接下载SDL2-2.0.0.zip 来自己编译,或者按自己的系统下载相关的开发资源(bin和include).解压资源包后,内容如下:     include目录