简介
之前写了一遍提取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;
}