一、概述
在《最简单的基于FFMPEG+SDL的音频播放器》记录一中,我们实现了音频的播放。更早前,我们在《最简单的基于FFMPEG+SDL的视频播放器》记录一和二中,实现了视频的播放。在实现视频播放的时候,我们设置了一个延迟40ms,否则视频就会以解码的速度去播放,很快速。在音频播放器中,我们有两个地方控制了播放音频的速度。第一个是采样率(http://wiki.libsdl.org/SDL_AudioSpec),采样率决定每秒送多少样本数据帧到音频设备,采样率和播放速度成正比。第二个是调用SDL_PauseAudio(0)播放音频后,有一个循环等待缓冲区数据播放完毕,才能进行下一帧数据帧的播放。
//Play
SDL_PauseAudio(0);
while (audio_len > 0)//Wait until finish
SDL_Delay(1);//ms
这两点就保证了音频的正常播放,而不用视频播放人为的去设定一个延迟时间保证播放速度。
这一次,我们将同时完成视频播放和音频播放。但我们将采用不同的方式去实现音频播放,核心内容跟”音频播放器“是一样的,只是我们采用了队列来保存解复用后的包,这样将解复用和解码两个过程分开,更加清晰,也有利于将来扩展更多的功能。
二、主要内容
1、首先、我们先描述一下我们将要用于保存包的队列,该结构体如下:
/*包队列*/
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;//包个数
int size;//包大小
SDL_mutex *mutex;//互斥量
SDL_cond *cond;//条件变量
} PacketQueue;
AVPacketList:AVPacketList是FFMPEG内置的结构体,是包的一个链表,结构体如下:
typedef struct AVPacketList {
AVPacket pkt;
struct AVPacketList *next;
} AVPacketList;
nb_packets:队列中包数量
size:队列中所有包的大小,以字节为单位
mutex和cond:互斥量和条件变量,因为音频播放是在另外一个线程中(想想那个callback),所以对于队列这个公共资源的读写需要互斥。如果对于互斥量和条件变量不熟悉可以参考生产者-消费者问题。
2、关于该队列的一些操作如下:
2.1、初始化队列
/*初始化队列*/
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));//将队列所在内存用0填充
q->mutex = SDL_CreateMutex();//创建互斥量和条件变量
q->cond = SDL_CreateCond();
}
2.2、入队
/*入队操作*/
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if (av_dup_packet(pkt) < 0) {//将pkt的内存复制到独立的内存中
return -1;
}
pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
if (!pkt1) return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);//先锁住队列,然后再入队
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;
SDL_CondSignal(q->cond);//唤醒被该条件变量阻塞的线程
SDL_UnlockMutex(q->mutex);//解锁资源
return 0;
}
2.3、出队
/*
出队操作
q: 队列
pkt: 出队的包
block: 是否人工阻塞
return: -1 退出 0 阻塞 1 成功
*/
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);//锁住队列,再出队
for (;;) {
if (quit) {//退出,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;
}
程序中的quit是一个全局变量,保证视频播放完成后可以正常退出,否则进程将无法结束。代码如下:
//当SDL退出时,设置quit=1
SDL_PollEvent(&event);
switch(event.type) {
case SDL_QUIT:
quit = 1;
....
剩下的事情就和原来一样了,读文件,读取流信息,解复用,解码......,特别的地方就是如何将解复用的包入队,和从队列取包,然后解码。
main函数源代码如下:
<pre name="code" class="cpp">int main(int argc, char *argv[]) {
struct SwsContext * sws_ctx = NULL;
AVFormatContext *pFormatCtx = NULL;
int i, videoStream, audioStream;
AVPacket packet;
int frameFinished;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVCodecContext *aCodecCtxOrig = NULL;
AVCodecContext *aCodecCtx = NULL;
AVCodec *aCodec = NULL;
SDL_Overlay *bmp;
SDL_Surface *screen;
SDL_Rect rect;
SDL_Event event;
SDL_AudioSpec wanted_spec, spec;
// Register all formats and codecs
av_register_all();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
//文件路径
char* filepath = "2.mp4";
// Open video file
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)
return -1; // Couldn't open file
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
return -1; // Couldn't find stream information
// Find the first video stream and audio stream
videoStream = -1;
audioStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
videoStream < 0) {
videoStream = i;
}
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO &&
audioStream < 0) {
audioStream = i;
}
}
if (videoStream == -1)
return -1; // Didn't find a video stream
if (audioStream == -1)
return -1;
//find the codeccontext
aCodecCtxOrig = pFormatCtx->streams[audioStream]->codec;
//get the decoder
aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
if (!aCodec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
// Copy context
aCodecCtx = avcodec_alloc_context3(aCodec);
if (avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1; // Error copying codec context
}
// Set audio settings from codec info
wanted_spec.freq = 44100;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;
//openaudio device
if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
//open the audio decoder
avcodec_open2(aCodecCtx, aCodec, NULL);
packet_queue_init(&audioq);
//play audio
SDL_PauseAudio(0);
//------------------------------------------------------------
//video part
// Get a pointer to the codec context for the video stream
pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
if (pCodec == NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1; // Error copying codec context
}
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
return -1; // Could not open codec
// Allocate video frame
pFrame = av_frame_alloc();
// Make a screen to put our video
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if (!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
// Allocate a place to put our YUV image on that screen
bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
pCodecCtx->height,
SDL_YV12_OVERLAY,//YVU模式
screen);
// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if (frameFinished) {
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
// Convert the image into YUV format that SDL uses
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
av_free_packet(&packet);
SDL_Delay(20);//延迟一下,防止播放太快
}
}
else if (packet.stream_index == audioStream) {
packet_queue_put(&audioq, &packet);//音频包入队
}
else {
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
//事件处理,SDL视频播放完成后,会触发SDL_QUIT事件
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit = 1;
SDL_Quit();
exit(0);
break;
default:
break;
}
}
//close the video and audio context
avcodec_close(pCodecCtxOrig);
avcodec_close(pCodecCtx);
avcodec_close(aCodecCtxOrig);
avcodec_close(aCodecCtx);
// Close the video file
avformat_close_input(&pFormatCtx);
return 0;
}
视频部分的代码如果有不懂的,可以回去看看: 最简单的视频播放器记录一 最简单的视频播放器记录二
音频回调函数如下:
/*
音频回调函数
userdata:in 编解码上下文
stream: out 播放的缓冲区
len: 缓冲区大小
*/
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(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) {
/* We have already sent all our data; get more */
audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));//解码包
if (audio_size < 0) {
/* If error, output silence */
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;
}
}
音频解码函数如下:
/*
aCodecCtx:编解码上下文
audio_buf:out 音频缓冲区
buf_size: 缓冲区大小
return: -1 错误, data_size 音频原始数据大小
*/
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;
static AVFrame frame;
int len1;//解码消费了包多少字节数据
int data_size = 0;
//新版中avcodec_decode_audio4()解码后输出的音频采样数据格式为AV_SAMPLE_FMT_FLTP(float, planar)而不再是AV_SAMPLE_FMT_S16(signed 16 bits)。因此 //无法直接使用SDL进行播放,必须使用SwrContext对音频采样数据进行转换之后,再进行输出播放。
//输出参数设置
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;//立体声
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);//根据通道布局类型获取通道数
int out_nb_samples = 1024;//单个通道样本个数,需要根据音频封装格式动态改变,不然某些格式的音频文件播放会有杂音
AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//采样格式,SDL可以播放此种格式
int out_sample_rate = 44100;//输出时采样率,CD一般为44100HZ
//获取声道布局,一些codec的channel_layout可能会丢失,这里需要重新获取一次
uint64_t in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels);
//输出缓存
uint8_t *out_buffer_audio = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);//*2是保证输出缓存大于输入数据大小
//音频格式转换设置
struct SwrContext *au_convert_ctx;
au_convert_ctx = swr_alloc();
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,in_channel_layout, aCodecCtx->sample_fmt, aCodecCtx->sample_rate, 0, NULL);
swr_init(au_convert_ctx);
for (;;) {
while (audio_pkt_size > 0) { //不停的解码,直到一个包的数据都被解码完毕
int got_frame = 0;
len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
if (len1 < 0) { /* if error, skip frame */
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;//剩余数据起始位置
audio_pkt_size -= len1;//剩余的字节数
data_size = 0; if (got_frame) { //动态改变输出样本数,保证有些文件不会出现杂音
if (out_nb_samples != frame.nb_samples){out_nb_samples = frame.nb_samples;}
//计算原始数据所需空间
data_size = av_samples_get_buffer_size(NULL,aCodecCtx->channels,out_nb_samples,out_sample_fmt,1);
//转换格式,否则SDL不能播放
swr_convert(au_convert_ctx, &out_buffer_audio, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame.data, frame.nb_samples);
assert(data_size <= buf_size);
memcpy(audio_buf, out_buffer_audio, data_size);//将数据拷贝到输出缓冲区
av_free(au_convert_ctx);
}
if (data_size <= 0) { /* No data yet, get more frames */
continue;
}
/* We have data, return it and come back for more later */
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;//<span style="font-family:Arial,Helvetica,sans-serif">audio_pkt_data 指向数据地址</span>
audio_pkt_size = pkt.size;//<span style="font-family:Arial,Helvetica,sans-serif">audio_pkt_size=包大小</span>
}}
运行结果:
运行后,可以看到视频和音频都可以播放了。但是视频和音频并不同步,因为视频我们采用的是延迟20ms,所以看起来有点偏快。音频是正常的,因为有采样率控制着播放的速度。如果视频延迟时间再慢一点,就会影响到音频的播放,听起来就会有卡顿。
所以,如果还要继续优化,那么视频播放应该在另外的线程中进行。而且还要考虑视频和音频的同步,比如将视频同步到音频。
最后再将整个源代码贴上来:
extern"C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include "include/sdl/SDL.h"
#include "include/sdl/SDL_thread.h"
#include "include/libavutil/time.h"
#include "include/libavutil/avstring.h"
#include "libswresample/swresample.h"
}
#pragma comment(lib, "lib/avformat.lib")
#pragma comment(lib, "lib/avcodec.lib")
#pragma comment(lib, "lib/avutil.lib")
#pragma comment(lib, "lib/swscale.lib")
#pragma comment(lib, "lib/swresample.lib")
#pragma comment(lib, "lib/SDL.lib")
#pragma comment(lib, "lib/SDLmain.lib")
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <assert.h>
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
/*包队列*/
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;//包个数
int size;//包大小
SDL_mutex *mutex;//互斥量
SDL_cond *cond;//条件量
} PacketQueue;
PacketQueue audioq;//音频包队列
int quit = 0;//是否退出
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if (av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
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;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
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;
}
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;
static AVFrame frame;
int len1, data_size = 0;
//音频转换数据格式,否则输出是杂音
//---------------------------------------------------------------------------
//输出参数设置
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;//立体声
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);//根据通道布局类型获取通道数
int out_nb_samples = 1024;//单个通道样本个数,需要根据音频封装格式动态改变,不然不同格式的文件音频播放速度会不同
AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//采样格式
int out_sample_rate = 44100;//输出时采样率,CD一般为44100HZ
//获取声道布局,一些codec的channel_layout可能会丢失,这里需要重新获取一次
uint64_t in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels);
//输出缓存
uint8_t *out_buffer_audio = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);//*2是保证输出缓存大于输入数据大小
//音频格式转换设置
struct SwrContext *au_convert_ctx;
au_convert_ctx = swr_alloc();
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,
in_channel_layout, aCodecCtx->sample_fmt, aCodecCtx->sample_rate, 0, NULL);
swr_init(au_convert_ctx);
for (;;) {
while (audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
if (len1 < 0) {
/* if error, skip frame */
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1;
data_size = 0;
if (got_frame) {
//FIX:FLAC,MP3,AAC Different number of samples
if (out_nb_samples != frame.nb_samples){
out_nb_samples = frame.nb_samples;
}
data_size = av_samples_get_buffer_size(NULL,
aCodecCtx->channels,
out_nb_samples,
out_sample_fmt,
1);
//转换格式,否则是杂音
swr_convert(au_convert_ctx, &out_buffer_audio, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame.data, frame.nb_samples);
assert(data_size <= buf_size);
memcpy(audio_buf, out_buffer_audio, data_size);
av_free(au_convert_ctx);
}
if (data_size <= 0) {
/* No data yet, get more frames */
continue;
}
/* We have data, return it and come back for more later */
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;
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
int len1, audio_size;
static uint8_t audio_buf[(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) {
/* We have already sent all our data; get more */
audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
if (audio_size < 0) {
/* If error, output silence */
audio_buf_size = 1024; // arbitrary?
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;
SDL_Delay(1);
}
}
int main(int argc, char *argv[]) {
struct SwsContext * sws_ctx = NULL;
AVFormatContext *pFormatCtx = NULL;
int i, videoStream, audioStream;
AVPacket packet;
int frameFinished;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVFrame *pFrame = NULL;
AVCodecContext *aCodecCtxOrig = NULL;
AVCodecContext *aCodecCtx = NULL;
AVCodec *aCodec = NULL;
SDL_Overlay *bmp;
SDL_Surface *screen;
SDL_Rect rect;
SDL_Event event;
SDL_AudioSpec wanted_spec, spec;
// Register all formats and codecs
av_register_all();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
//文件路径
char* filepath = "2.mp4";
// Open video file
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)
return -1; // Couldn't open file
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
return -1; // Couldn't find stream information
// Find the first video stream and audio stream
videoStream = -1;
audioStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
videoStream < 0) {
videoStream = i;
}
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO &&
audioStream < 0) {
audioStream = i;
}
}
if (videoStream == -1)
return -1; // Didn't find a video stream
if (audioStream == -1)
return -1;
//find the codeccontext
aCodecCtxOrig = pFormatCtx->streams[audioStream]->codec;
//get the decoder
aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
if (!aCodec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
// Copy context
aCodecCtx = avcodec_alloc_context3(aCodec);
if (avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1; // Error copying codec context
}
// Set audio settings from codec info
wanted_spec.freq = 44100;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;
//openaudio device
if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
//open the audio decoder
avcodec_open2(aCodecCtx, aCodec, NULL);
packet_queue_init(&audioq);
//play audio
SDL_PauseAudio(0);
//------------------------------------------------------------
//video part
// Get a pointer to the codec context for the video stream
pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
if (pCodec == NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1; // Error copying codec context
}
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
return -1; // Could not open codec
// Allocate video frame
pFrame = av_frame_alloc();
// Make a screen to put our video
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if (!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
// Allocate a place to put our YUV image on that screen
bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
pCodecCtx->height,
SDL_YV12_OVERLAY,//YVU模式
screen);
// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_YUV420P,
SWS_BILINEAR,
NULL,
NULL,
NULL
);
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if (frameFinished) {
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
// Convert the image into YUV format that SDL uses
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_DisplayYUVOverlay(bmp, &rect);
av_free_packet(&packet);
SDL_Delay(20);//延迟一下,防止播放太快
}
}
else if (packet.stream_index == audioStream) {
packet_queue_put(&audioq, &packet);//音频包入队
}
else {
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit = 1;
SDL_Quit();
exit(0);
break;
default:
break;
}
}
//close the video and audio context
avcodec_close(pCodecCtxOrig);
avcodec_close(pCodecCtx);
avcodec_close(aCodecCtxOrig);
avcodec_close(aCodecCtx);
// Close the video file
avformat_close_input(&pFormatCtx);
return 0;
}
参考链接:
http://dranger.com/ffmpeg/tutorial03.html