FFMPeg 多线程 简易播放器功能实现(非视音同步)
视频播放主要原理基于多线程和队列进行解耦
废话不多说直接来Code
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
//自定义事件
//刷新画面
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 2
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
AVFormatContext * fmtCtx;
int audioIndex,videoIndex;
AVStream * audioSm = NULL;
AVStream *videoSm = NULL;
AVCodec * audioCodec = NULL;
AVCodec * videoCodec = NULL;
AVCodecContext* audioCodecCtx = NULL;
AVCodecContext* videoCodecCtx = NULL;
AVFrame * videoFrame = NULL;
AVFrame * pFrameYUV= NULL;
AVFrame * audioFrame;
unsigned char * out_buffer;
struct SwsContext * swsCtx;
SDL_Window * screen;
SDL_Renderer * render;
SDL_Texture * texture;
SDL_Rect rect;
//SDL_Event event;
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
AVFrame *frame;
int width, height; /* source height & width */
int allocated;
} VideoPicture;
SDL_cond *vpCon;
SDL_mutex *vpMutex;
VideoPicture vp[VIDEO_PICTURE_QUEUE_SIZE];
int vpIndex = -1;
int vpSize = 0;
unsigned char *audioBuff = NULL;
int audioIndex = 0;
int audioSize = 0;
int audioLastIndex = 0;
SDL_mutex *lock;
SDL_cond *con;
SwrContext *swr;
struct SwrContext *au_convert_ctx;
unsigned int audioLen = 0;
unsigned char *audioChunk = NULL;
unsigned char *audioPos = NULL;
SDL_AudioSpec audioSpec;
SDL_AudioSpec audioSpec1;
int out_buffer_size = -1;
PacketQueue videoQ;
PacketQueue audioQ;
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 = 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 quit = 0;
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;
}
void audioCallBack(void *userdata, Uint8 *stream, int len){
SDL_memset(stream, 0, len);
if (audioLen == 0)
return;
len = (len>audioLen?audioLen:len);
SDL_MixAudio(stream,audioPos,len,SDL_MIX_MAXVOLUME);
audioPos += len;
audioLen -= len;
// SDL_Delay(10);
}
queneVP(AVFrame *f){
SDL_LockMutex(vpMutex);
if((vpIndex+1)>=VIDEO_PICTURE_QUEUE_SIZE){
SDL_CondWait(vpCon,vpMutex);
}
vpIndex++;
vp[vpIndex].frame = f;
SDL_UnlockMutex(vpMutex);
}
int thread_exit=0;
//Thread
int sfp_refresh_thread(void *opaque)
{
while (thread_exit==0) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
//Wait 40 ms
SDL_Delay(40);
}
return 0;
}
int ComponentOpen(){
char * url = "D:/电影/9.mp4";
int ret = 0;
ret = avformat_open_input(&fmtCtx,url,NULL,NULL);
if(ret<0){
printf("open the file %s failed",url);
return -1;
}
ret = avformat_find_stream_info(fmtCtx,NULL);
if(ret<0){
printf("find stream info failed");
return -1;
}
ret = av_find_best_stream(fmtCtx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,1);
if(ret<0){
printf("find video stream index failed");
return -1;
}else{
videoIndex = ret;
videoSm = fmtCtx->streams[videoIndex];
}
ret = av_find_best_stream(fmtCtx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,1);
if(ret<0){
printf("find audio stream index failed");
return -1;
}else{
audioIndex = ret;
audioSm = fmtCtx->streams[audioIndex];
}
videoCodec = avcodec_find_decoder(videoSm->codecpar->codec_id);
if(!videoCodec){
printf("find video decoder failed");
return -1;
}
videoCodecCtx = avcodec_alloc_context3(videoCodec);
if(!videoCodecCtx){
printf("alloc video decoder CTX failed");
return -1;
}
audioCodec = avcodec_find_decoder(audioSm->codecpar->codec_id);
if(!audioCodec){
printf("find audio decoder failed");
return -1;
}
audioCodecCtx = avcodec_alloc_context3(audioCodec);
if(!audioCodecCtx){
printf("alloc audio decoder CTX failed");
return -1;
}
if(avcodec_parameters_to_context(videoCodecCtx,videoSm->codecpar)<0){
fprintf(stderr,"copy parameters to context failed!");
return -1;
}
ret = avcodec_open2(videoCodecCtx,videoCodec,NULL);
if(ret<0){
printf("video decoder open failed!");
return -1;
}
if(avcodec_parameters_to_context(audioCodecCtx,audioSm->codecpar)<0){
fprintf(stderr,"copy parameters to context failed!");
return -1;
}
ret = avcodec_open2(audioCodecCtx,audioCodec,NULL);
if(ret<0){
printf("audio decoder open failed!");
return -1;
}
return 1;
}
int displayVideo(AVFrame * frame){
SDL_UpdateYUVTexture(texture, &rect,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
//fprintf(stdout,"121................");
SDL_RenderClear( render );
SDL_RenderCopy( render, texture, NULL, &rect);
SDL_RenderPresent( render );
//fprintf(stdout,"122................");
//SDL_Delay(40);
}
doDisplayVideo(){
SDL_LockMutex(vpMutex);
if(vpIndex>=0){
displayVideo(vp[vpIndex].frame);
vpIndex-- ;
}
SDL_CondSignal(vpCon);
SDL_UnlockMutex(vpMutex);
}
doQueneVp(){
while(1){
int ret = 0;
AVPacket * pkt = av_packet_alloc();
ret = packet_queue_get(&videoQ,pkt,0);
if(ret <=0){
continue;
}
ret = avcodec_send_packet(videoCodecCtx,pkt);
if(ret < 0){
printf("Error sending a packet for decoding\n");
exit(1);
}
av_packet_unref(pkt);
ret = avcodec_receive_frame(videoCodecCtx,videoFrame);
if(ret == 0 ){
ret = sws_scale(swsCtx,videoFrame->data,videoFrame->linesize,0,videoCodecCtx->height,pFrameYUV->data,pFrameYUV->linesize);
queneVP(pFrameYUV);
}
}
}
int readFrame(){
SDL_Event event;
AVPacket *pkt = av_packet_alloc();
int ret = 0;
packet_queue_init(&videoQ);
packet_queue_init(&audioQ);
while(1){
if(av_read_frame(fmtCtx,pkt)>=0){
if(pkt->stream_index == videoIndex){
packet_queue_put(&videoQ,pkt);
}
if(pkt->stream_index == audioIndex){
packet_queue_put(&audioQ,av_packet_clone(pkt));
}
}
}
av_free_packet(pkt);
}
initVideoFrame(){
videoFrame = av_frame_alloc();
pFrameYUV=av_frame_alloc();
out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, videoCodecCtx->width, videoCodecCtx->height,1));
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,
AV_PIX_FMT_YUV420P,videoCodecCtx->width, videoCodecCtx->height,1);
swsCtx = sws_getContext(videoCodecCtx->width,videoCodecCtx->height,videoCodecCtx->pix_fmt,videoCodecCtx->width,videoCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
}
initSDLForVideo(){
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
int screenW;
int screenH;
screenW=videoCodecCtx->width;
screenH = videoCodecCtx->height;
rect.x=0;
rect.y=0;
rect.w=screenW;
rect.h=screenH;
screen = SDL_CreateWindow("my first video",SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screenW,screenH,SDL_WINDOW_OPENGL);
render = SDL_CreateRenderer(screen,-1,0);
texture = SDL_CreateTexture(render,SDL_PIXELFORMAT_IYUV,SDL_TEXTUREACCESS_STREAMING,screenW,screenH);
}
closeVideo(){
avformat_close_input(fmtCtx);
av_frame_free(&pFrameYUV);
av_frame_free(&videoFrame);
av_frame_free(&audioFrame);
avcodec_close(videoCodecCtx);
}
initAudio(){
uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO; //通道布局 输出双声道
enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; //声音格式
int out_sample_rate=44100; //采样率
int out_nb_samples = -1;
int out_channels = -1; //通道数
uint64_t in_chn_layout = -1; //通道布局
audioFrame = av_frame_alloc();
out_nb_samples = audioCodecCtx->frame_size;
out_channels = av_get_channel_layout_nb_channels(out_chn_layout);
out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
audioBuff = (unsigned char *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //双声道
printf("-------->out_buffer_size is %d\n",out_buffer_size);
in_chn_layout = av_get_default_channel_layout(audioCodecCtx->channels);
audioSpec.callback=audioCallBack;
audioSpec.channels=audioCodecCtx->channels;
audioSpec.format = AUDIO_S16SYS;
audioSpec.silence = 0;
audioSpec.freq= audioCodecCtx->sample_rate;
audioSpec.userdata = audioCodecCtx;
audioSpec.samples = SDL_AUDIO_BUFFER_SIZE;
au_convert_ctx=swr_alloc_set_opts(NULL,
out_chn_layout, /*out*/
out_sample_fmt, /*out*/
out_sample_rate, /*out*/
in_chn_layout, /*in*/
audioCodecCtx->sample_fmt , /*in*/
audioCodecCtx->sample_rate, /*in*/
0,
NULL);
swr_init(au_convert_ctx);
SDL_OpenAudio(&audioSpec,NULL);
SDL_PauseAudio(0);
}
doAudioPlay(){
while(1){
int ret =0;
AVPacket * pkt = av_packet_alloc();
ret = packet_queue_get(&audioQ,pkt,0);
if(ret <=0){
continue;
}
ret = avcodec_send_packet(audioCodecCtx,pkt);
if(ret < 0){
printf("Error sending a packet for decoding\n");
exit(1);
}
av_packet_unref(pkt);
ret = avcodec_receive_frame(audioCodecCtx,audioFrame);
if(ret==0){
int nb = swr_convert(au_convert_ctx,&audioBuff, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)audioFrame->data , audioFrame->nb_samples);
av_samples_get_buffer_size(NULL,audioCodecCtx->channels,audioFrame->nb_samples,audioCodecCtx->sample_fmt,1);
while(audioLen>0){
SDL_Delay(1);
}
audioChunk = (unsigned char *)audioBuff;
audioPos = audioChunk;
audioLen = out_buffer_size;
}
}
}
int main(int args,char * argv[]){
ComponentOpen();
initAudio();
initVideoFrame();
initSDLForVideo();
vpMutex = SDL_CreateMutex();
vpCon = SDL_CreateCond();
SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread,"videoRefresh",NULL);
SDL_Thread *readFrame_tid = SDL_CreateThread(readFrame,"readFrame",NULL);
SDL_Thread *doQueneVp_tid = SDL_CreateThread(doQueneVp,"doQueneVp",NULL);
SDL_Thread *doAudioPlay_tid = SDL_CreateThread(doAudioPlay,"doAudioPlay",NULL);
SDL_Event event;
for(;;){
SDL_WaitEvent(&event);
if(event.type==SFM_REFRESH_EVENT){
doDisplayVideo();
}
}
printf("the video is end ....");
SDL_Quit();
closeVideo();
avformat_close_input(&fmtCtx);
}
参考文献:雷霄骅 大神FFMpeg系列
FFMpeg 官方例子
FFMPEG 旅行例子
编写工具:codeblocks,c语言,sdl2,ffmpeg lib
操作系统:win10 64位
如有转载请附上来源谢谢!