ffmpeg和SDL学习笔记
根据ffmpeg官方网站上的例子程序开始学习ffmpeg和SDL编程。
SDL是一个跨平台的多媒体开发包。适用于游戏,模拟器,播放器等应用软件开发。支持linux 、win32 等操作系统。
主要应用:
视频
|
事件
- 提供以下事件:
- 应用程序的visibility发生改变
- 键盘输入
- 鼠标输入
- 用户要求的退出
- 每种事件都能通过SDL_EventState()关闭或者打开。
- 事件经由用户指定的过滤函数再被加入到内部的事件队列。
- 线程安全的事件队列。
音频
- 设置8位和16位的音频,单声道或者立体声,如果格式硬件不支持,可以选择转换。
- 由独立的线程执行音频部分,并提供用户回调(callback)机制。
- 设计上考虑到了客户定制的软混音器,但实际上在例程中就包含了一个完整的音频/音乐输出库。
CD音频
- 完整的CD音频控制API
线程
- 简单的线程创建API
- 用于同步的简单的二进制信号量(semaphores)
定时器
- 读取已流逝的毫秒数。
- 等待指定的毫秒数。
- 设置一个10毫秒精度的周期性定时器。
字节序无关
- 侦测当前系统的字节序
- 快速转换数据的函数
- 读写指定字节序的数据
这里我们使用SDL作为音视频输出对象,ffmpeg完成音视频的解码。
像使用其他软件包或者开发库一样,首先肯定要初始化相应的库,然后才能够使用,初始化函数如下:
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
fprintf(stderr, "Could not initialize SDL - %s/n", SDL_GetError());
return -1 ;
}
SDL有很多方法是实现视频的输出,但是YUV overlay是一种简单而又常用的方法,具体使用方法是:
首先创建一个surface用来显示视频数据,然后创建一个overlay,然后就可以通过overlay输出视频到surface
其创建过程如下:
int init_sdl(int width ,int height)
{
//create screen for displaying
screen = SDL_SetVideoMode(width, height, 0, 0);
if(!screen)
{
fprintf(stderr, "SDL: could not set video mode - exiting/n");
return -1 ;
}
//Now we create a YUV overlay on that screen so we can input video to it:
bmp = SDL_CreateYUVOverlay(width, height,
SDL_YV12_OVERLAY, screen);
return 0 ;
}
创建后就可以显示视频数据了,我对此进行了简单的封装,如下:
//显示函数,提取一个完整的视频帧后,就可以显示此函数
void sdl_display(AVPicture *pict,SDL_Overlay *bmp,enum PixelFormat src_fmt,int width,int height)
{
SDL_Rect rect ;
struct SwsContext *img_convert_ctx=NULL;
AVPicture p;
SDL_LockYUVOverlay(bmp);
p.data[0] = bmp->pixels[0];
p.data[1] = bmp->pixels[2];
p.data[2] = bmp->pixels[1];
p.linesize[0] = bmp->pitches[0];
p.linesize[1] = bmp->pitches[2];
p.linesize[2] = bmp->pitches[1];
//视频格式转化为YUV420P格式
img_convert_ctx=sws_getCachedContext(img_convert_ctx,width,height,
src_fmt,width,height,PIX_FMT_YUV420P,
SWS_X ,NULL,NULL,NULL) ;
if (img_convert_ctx == NULL)
{
printf("can't init convert context!/n") ;
return ;
}
sws_scale(img_convert_ctx, pict->data, pict->linesize,
0,width, p.data, p.linesize);
SDL_UnlockYUVOverlay(bmp);
//设置显示区域的位置和大小
rect.x = 0;
rect.y = 0;
rect.w = width;
rect.h = height;
//显示视频帧
SDL_DisplayYUVOverlay(bmp, &rect);
}
这样在解码出一帧数据后就可以通过调用此函数完成视频的显示了
视频显示搞定了,那么该轮到音频输出
要想输出音频,首先必须得打开音频设备,SDL对音频设备的打开和初始化已经做好了封装,我们通过调用SDL_OpenAudio 来打开和初始化音频设备,通过结构体 SDL_AudioSpec 设置相应的参数,然后将参数通过 SDL_OpenAudio 设置好设备,封装如下:
SDL_AudioSpec audio_spec ,spec;
int init_sdl_audio(AVCodecContext *aCodecCtx)
{
audio_spec.freq = aCodecCtx->sample_rate;
audio_spec.format = AUDIO_S16SYS;
audio_spec.channels = aCodecCtx->channels;
audio_spec.silence = 0;
audio_spec.samples = SDL_AUDIO_BUFFER_SIZE;
audio_spec.callback = audio_callback;
audio_spec.userdata = aCodecCtx;
if(SDL_OpenAudio(&audio_spec, &spec) < 0)
{
fprintf(stderr, "SDL_OpenAudio: %s/n", SDL_GetError());
return -1;
}
return 0 ;
}
其他就和视频一样了,先分解出音频流,然后根据音频流找出解码上下文,再根据解码上下文找到解码器,并打开了,接着就可以进行解码了。
但是我们不能想解码视频一样,直接对音频包进行解码,我们不断从文件中的packet,同时SDL又要不断的调用回调函数,解决的办法是创建一个互斥队列,ffmpeg已经为我们封装了一个AVPacketList结构体,我们需要对此进行再次封装如下:
typedef struct PacketQueue {
AVPacketList
*first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex
*mutex;
SDL_cond
*cond;
} PacketQueue;
我们得注意:这里的size是packet的大小,而nb_packets是队列中packet的个数。
对于一个队列首先得有一个初始化函数,完成初始化
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex
();
q->cond = SDL_CreateCond
();
}
很明显这个初始化函数完成了队列的内存分配、互斥量和条件量的创建。
然后就是入队和出队的函数
//put audio packet in the queue
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
//if pkt is not allocated ,allocate it
if(av_dup_packet(pkt) < 0) {
return -1;
}
//allocate space for new member of queue
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
//put pkt in pkt1
pkt1->pkt = *pkt;
pkt1->next = NULL;
//lock queue and wait until finishing put
SDL_LockMutex(q->mutex);
//if last_pkt is NULL,it means that the queue is NULL,so put the packet in the first position
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
//send signal of finish
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
//put audio packet in the queue
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
这里我们必须得注意SDL为音频处理创建了一个单独的线程,线程中通过调用回调函数完成从包中解码出音频帧
然后再调用解码函数将音频帧解码出来!
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0;
while(len > 0) {
if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf,
sizeof(audio_buf));
if(audio_size < 0) {
audio_buf_size = 1024;
memset(audio_buf, 0, audio_buf_size);
} else {
audio_buf_size = audio_size;
}
audio_buf_index = 0;
}
len1 = audio_buf_size - audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}
解码函数
//decode audio frame
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
int buf_size) {
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;
int len1, data_size;
for(;;) {
while(audio_pkt_size > 0) {
data_size = buf_size;
len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size,
audio_pkt_data, audio_pkt_size);
if(len1 < 0) {
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1;
if(data_size <= 0) {
continue;
}
return data_size;
}
if(pkt.data)
av_free_packet(&pkt);
if(quit) {
return -1;
}
if(packet_queue_get(&audioq, &pkt, 1) < 0) {
return -1;
}
audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
//主函数
int main()
{
AVFormatContext *pFormatCtx ;
AVCodecContext *pCodecCtx,*aCodecCtx ;
AVCodec *pCodec,*aCodec ;
AVStream *st;
AVFrame *pFrame ;
AVPacket packet ;
struct SwsContext *img_convert_ctx=NULL;
SDL_Event event;
uint8_t *buffer ;
SDL_Rect rect ;
char *filename="1.asf" ;
int ret,i,videoStream,audioStream,numBytes,frameFinished ;
//init sdl library with video and audio
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
fprintf(stderr, "Could not initialize SDL - %s/n", SDL_GetError());
return -1 ;
}
//init the format and codec library
av_register_all() ;
ret=av_open_input_file(&pFormatCtx,filename,NULL,0,NULL) ;
if(ret<0)
{
printf("Error1:open input file failed!/n") ;
return -1 ;
}
//retrive stream information
ret=av_find_stream_info(pFormatCtx) ;
if(ret<0)
{
printf("Error2:find stream information failed!/n") ;
return -1 ;
}
//dump information about file onto standard error
dump_format(pFormatCtx,0,filename,0) ;
videoStream=-1 ;
audioStream=-1 ;
for(i=0; i < pFormatCtx->nb_streams; i++)
{
if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO
&&videoStream < 0)
{
videoStream=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO &&
audioStream < 0)
{
audioStream=i;
}
}
//check whether find video stream and audio stream
if(videoStream==-1)
{
printf("Error3:can't find video stream!/n") ;
return -1 ;
}
if(audioStream==-1)
{
printf("Error4:can't find audio stream!/n") ;
return -1 ;
}
//get video codec context
st=pFormatCtx->streams[videoStream] ;
pCodecCtx=st->codec;
//get audio codec contex
aCodecCtx=pFormatCtx->streams[audioStream] ->codec;
//find video codec
pCodec=avcodec_find_decoder(pCodecCtx->codec_id) ;
if(pCodec==NULL)
{
printf("Error5:can't find video decoder!/n") ;
return -1 ;
}
//open video decoder
ret=avcodec_open(pCodecCtx,pCodec) ;
if(ret<0)
{
printf("open video decoder failed!/n") ;
return -1 ;
}
// Allocate video frame
pFrame=avcodec_alloc_frame();
//init audio codec context
if(init_sdl_audio(aCodecCtx)<0)
{
printf("Error6:init sdl audio failed!/n") ;
return -1 ;
}
//find audio codec
aCodec=avcodec_find_decoder(aCodecCtx->codec_id) ;
if(aCodec==NULL)
{
printf("Error7:can't find audio decoder!/n") ;
return -1 ;
}
//open audio decoder
ret=avcodec_open(aCodecCtx, aCodec);
if(ret<0)
{
printf("Error8:open audio decoder failed!/n") ;
return -1 ;
}
//init audio packet queue
packet_queue_init(&audioq);
SDL_PauseAudio(0);
//init overalay
if(init_sdl(pCodecCtx->width,pCodecCtx->height)<0)
{
printf("Error9:init sdl library failed!/n") ;
return -1 ;
}
//不知道为什么
url_set_interrupt_cb(decode_interrupt_cb);
//decode video and audio frame
while(av_read_frame(pFormatCtx,&packet)>=0)
{
if(packet.stream_index==videoStream)
{
//decode a frame
avcodec_decode_video(pCodecCtx,pFrame,&frameFinished,packet.data,packet.size) ;
//finish or not ?
if(frameFinished)
{
sdl_display((AVPicture *)pFrame,bmp,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height) ;
}
}else if(packet.stream_index==audioStream)
{
packet_queue_put(&audioq, &packet);
}else
{
av_free_packet(&packet) ;
}
SDL_PollEvent(&event);
switch(event.type)
{
case SDL_QUIT:
quit = 1;
SDL_Quit();
exit(0);
break;
default:
break;
}
}
// Free the YUV frame
av_free(pFrame);
// Close the codec
avcodec_close(pCodecCtx);
// Close the video file
av_close_input_file(pFormatCtx);
return 0;
}
但是这个程序没有解决音视频同步等问题,视频数据显示很快!
ffmpeg和SDL学习笔记相关推荐
- 雷神FFMpeg源码学习笔记
雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...
- 【SDL】SDL学习笔记二 定时器
定时器相关API(SDL_timer.h): 1. Uint32 SDL_GetTicks(): 获取从SDL库初始化(定时器模块初始化)开始到当前的运行时间(ms); 2. Uint64 S ...
- 【SDL】SDL学习笔记一 SDL的子系统的初始化和退出
使用SDL库之前需要装载各个子系统,就像要放电影,必须给播放器提供影片一样,程序退出的时候,应该先退出子系统,释放资源. 1. 初始化SDL函数 该函数必须是在程序开始的地方调用 int SDL_In ...
- ffmpeg源码学习笔记三
9. 关于如何parse mkv 前面为了不把战线拉太长,把如何parse mkv container 内容直接跳过了 接下来还是从read_header 开始讲解 static int matros ...
- ffmpeg源码学习笔记五
14.read_frame_internal static int read_frame_internal(AVFormatContext *s, AVPacket *pkt) {...//初始化pa ...
- FFmpeg基础到工程-多路H265监控录放开发学习笔记
多路H265监控录放开发学习笔记 课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等.包括:音视频.流媒体.直播.Android.视频监控28181.等. 具体 ...
- NDK学习笔记:FFmpeg解压MP34提取音频PCM(swrContext、swr_alloc_set_opts)
NDK学习笔记:FFmpeg解压MP34提取音频PCM 承接 FFmpeg解压MP4提取视频YUV ,这次我们需要提取的是音频原始数据PCM.代码流程大同小异,主要区别就是AVFrame->PC ...
- NDK学习笔记:FFmpeg音视频同步3(你追我赶,升级ffmpeg/libyuv支持neon)
NDK学习笔记:FFmpeg音视频同步3 本篇内容说多不多,但如果要说得明明白白的,可能就有点难度了.所以我决定把我的调试过程日志都呈现出来,方便大家理解.继上一篇文末,我们学习到了什么是DTS/PT ...
- ffmpeg学习笔记
对于每一个刚開始学习的人,刚開始接触ffmpeg时,想必会有三个问题最为关心,即ffmpeg是什么?能干什么?怎么開始学习?本人前段时间開始接触ffmpeg,在刚開始学习过程中.这三个问 ...
最新文章
- GET POST 区别详解
- “2016智能终端峰会新闻发布会暨移动信息化可信选型认证结果发布会” —— 在京成功召开...
- cout的输出格式初探
- 程序员食品营养(1)-面包基础
- RStudio(You‘re using a non-UTF8 locale, therefore only ASCII characters will work)
- 机器人抓取方式,值得研究。
- 【UIKit】UITableView 3
- ASP.NET 自定义项目模板
- [译] 想帮助用户做决定?你的APP可以这样设计!
- ​再现Bug?iPhone13 拍照翻车自带炫屏 网友:没想到我也“中奖”了
- Python运行效率低的原因
- “本是青灯不归客,却因浊酒留风尘,星光不问赶路人,岁月不负有心人”,你是怎么理解的?
- 用the_excerpt处理中文文章字数限制的方法
- 最接近win7的Linux系统,Win7的优势所在 - Ubuntu PK Win7旗舰版到底还差多少火候_Linux新闻_Linux公社-Linux系统门户网站...
- win7系统安装信息服务器不可用怎么办,win7系统rpc服务器不可用怎么办
- Linux的任督二脉:进程调度和内存管理
- 都严肃点!史上最早的“喜当爹”其实是一项国家计划
- mysql 恢复到新库_mysql恢复到一个新的mysql主和从数据库教程
- 清华王宏计算机编程,培养出300多个信息学奥赛冠军后,听他谈如何造就精英!...
- IE 播放wav文件
热门文章
- 数据库事务隔离级别-- 脏读、幻读、不可重复读(清晰解释)
- rsyslog syslog详解
- 【手算】行列式树形展开
- Python 数据分析三剑客之 NumPy(五):数学 / 算术 / 统计 / 排序 / 条件 / 判断函数合集
- java redis 生成唯一id_Redis在集群环境中生成唯一ID
- CCIE-LAB-第一篇-教学导入环境
- 【HDU - 5706】GirlCat(bfs)
- 【CodeForces-1041C】Coffee Break(贪心,STL,set二分维护,题意难,有坑,SJ题,构造)(知识点总结)
- 图解算法学习笔记(五):散列表
- 小程序中input标签没有反应_鸢尾花预测:如何创建机器学习Web应用程序?