SDL播放视频
// PlayVideo.cpp : Defines the entry point for the console application. // extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> #include <libavutil/avstring.h> #include <libavutil/pixfmt.h> #include <libavutil/log.h> }; #include <windows.h> #include <stdio.h> #include <math.h> #include <SDL.h> #include <SDL_thread.h> #include <SDL_main.h> #pragma comment(lib, "libmingwex.lib") #pragma comment(lib, "libgcc.lib") #pragma comment(lib, "avformat.lib") #pragma comment(lib, "avutil.lib") #pragma comment(lib, "avcodec.lib") #pragma comment(lib, "swresample.lib") #pragma comment(lib, "swscale.lib") #pragma comment(lib, "SDL.lib") #pragma comment(lib, "SDLmain.lib") //speedloss is decided by scale //#define PICTURE_W 640 //#define PICTURE_H 352 //#define PICTURE_PW 640 //#define PICTURE_PH 352 //change 100 #define MAX_QUEUE_SIZE 50 #define VIDEO_PICTURE_QUEUE_SIZE 2 typedef struct VideoPicture_t{ SDL_Overlay *bmp; double pts; double duration; int width,height; }VideoPicture; struct play_info_t{ //先注释掉同步 double audio_clock;//解码时钟 double audio_current_pts;//当前音频的pts double vedio_clock;//解码时钟 double last_frame_pts;//上一帧的PTS值 double last_frame_delay;//上一帧的延时 double frame_timer; AVCodecContext *pVideoCodecCtx; AVCodecContext *pAudioCodecCtx; VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; int pictq_size, pictq_rindex, pictq_windex; SDL_mutex *pictq_mutex; SDL_cond *pictq_cond; struct SwrContext *audio_conv; struct SwsContext *video_conv; }; struct play_info_t g_play_info; typedef struct PacketQueue { AVPacketList *first_pkt, *last_pkt; int nb_packets; int size; SDL_mutex *mutex; SDL_cond *cond; } PacketQueue; PacketQueue audio_queue; PacketQueue video_queue; int quit = 0; SDL_Surface *screen; SDL_Overlay *bmp; SDL_Rect rect; int global_video_pkt_pts=0; int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) { int ret = avcodec_default_get_buffer(c, pic); uint64_t *pts = (uint64_t *)av_malloc(sizeof(uint64_t)); *pts = global_video_pkt_pts; pic->opaque = pts; return ret; } void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) { if(pic) av_freep(&pic->opaque); avcodec_default_release_buffer(c, pic); } double get_audio_clock(void) { return g_play_info.audio_current_pts; } double get_video_clock(void) { return g_play_info.vedio_clock; } void picture_queque_init(struct play_info_t *play) { int i; play->pictq_cond=SDL_CreateCond(); play->pictq_mutex=SDL_CreateMutex(); play->pictq_rindex=play->pictq_windex=0; play->pictq_size=0; memset(play->pictq,0,sizeof(play->pictq)); for(i=0;i<VIDEO_PICTURE_QUEUE_SIZE;i++){ //注释掉 g_play_info.pVideoCodecCtx->height //play->pictq[i].bmp=SDL_CreateYUVOverlay(PICTURE_W,PICTURE_H,SDL_YUY2_OVERLAY,screen); play->pictq[i].bmp=SDL_CreateYUVOverlay(g_play_info.pVideoCodecCtx->width,g_play_info.pVideoCodecCtx->height,SDL_YUY2_OVERLAY,screen); } } void picture_queque_destroy(struct play_info_t *play) { int i; for(i=0;i<VIDEO_PICTURE_QUEUE_SIZE;i++){ SDL_FreeYUVOverlay(play->pictq[i].bmp); } } 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; } 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; } int decode_interrupt_cb(void) { return quit; } //计算正确的pts值 double sync_video(struct play_info_t *play,AVFrame* frame,double pts) { double frame_delay; if(pts!=0) play->vedio_clock=pts; else pts=play->vedio_clock; frame_delay=av_q2d(play->pVideoCodecCtx->time_base);//一帧占用的时间 frame_delay+=frame->repeat_pict*(frame_delay*0.5);//计算重复帧 play->vedio_clock+=frame_delay; return pts; } int audio_decode_frame(struct play_info_t* play, uint8_t *audio_buf,int buf_size) { AVCodecContext *aCodecCtx=play->pAudioCodecCtx; AVFrame *pAudioFrame=avcodec_alloc_frame(); AVPacket pkt,pkt1; int frame_finished=0; int pkt_pos,pkt_len; int src_len=0,dst_len=0,data_size=0; float pts=0; avcodec_get_frame_defaults(pAudioFrame); uint8_t *out[]={audio_buf}; for(;!quit;){ if(packet_queue_get(&audio_queue, &pkt, 1) < 0) { av_free(pAudioFrame); return -1; } pkt1=pkt; pkt_pos=0; pkt_len=pkt.size; while(pkt_pos<pkt.size && !quit){ if((src_len=avcodec_decode_audio4(aCodecCtx,pAudioFrame,&frame_finished,&pkt1))<0){ av_free_packet(&pkt); av_free(pAudioFrame); return -1; } /* play->audio_clock+=(double)(pAudioFrame->linesize[0])/ (aCodecCtx->channels*aCodecCtx->sample_rate*av_get_bytes_per_sample(aCodecCtx->sample_fmt)); */ if(video_queue.nb_packets>= 100 || audio_queue.nb_packets>= 200){ //改写 //printf("%d---%d\n",video_queue.nb_packets,audio_queue.nb_packets); Sleep(20); // change 10000 } else if(video_queue.nb_packets>= 50 || audio_queue.nb_packets>= 100) { Sleep(10); } if(frame_finished){ const uint8_t **in=(const uint8_t **)pAudioFrame->data; int len=swr_convert(play->audio_conv,out,buf_size/aCodecCtx->channels/av_get_bytes_per_sample(AV_SAMPLE_FMT_S16), in,pAudioFrame->linesize[0]/aCodecCtx->channels/av_get_bytes_per_sample((AVSampleFormat)pAudioFrame->format)); len=len*aCodecCtx->channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); av_free(pAudioFrame); av_free_packet(&pkt); return len; }else{ if (!pkt1.data && aCodecCtx->codec->capabilities & CODEC_CAP_DELAY){ break; } } pkt_pos+=src_len;//已经解码的长度 pkt1.data=pkt.data+pkt_pos; pkt1.size=pkt.size-pkt_pos; } av_free_packet(&pkt); } av_free(pAudioFrame); return dst_len; } //decode the audio data ,and copy the result to stream void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len) { struct play_info_t *play=(struct play_info_t*)userdata; AVCodecContext *aCodecCtx = play->pAudioCodecCtx; 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; int bytes_per_sec=2*aCodecCtx->channels*aCodecCtx->sample_rate; while(len > 0 && !quit) { if(audio_buf_index >= audio_buf_size) { audio_size = audio_decode_frame(play, 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; } //先注释掉同步,自然播放 if(audio_buf_size > audio_buf_index){ play->audio_current_pts=play->audio_clock-((double)(audio_buf_size-audio_buf_index)/(double)bytes_per_sec); if(play->audio_current_pts<0) play->audio_current_pts=play->audio_clock; printf("audio-pts:%f\n",play->audio_current_pts); } } int SDLCALL video_decode_callback(void* arg) { printf("---in video decode thread ---\n"); struct play_info_t* play=(struct play_info_t*)arg; AVCodecContext *pVideoCodecCtx=play->pVideoCodecCtx; double pts; VideoPicture *vp; AVPacket pkt; int frame_finished; AVPicture pict = { { 0 } }; AVFrame *pVideoFrame=avcodec_alloc_frame(); if(pVideoFrame==NULL){ printf("can't alloc video frame!\n"); return -1; } while(!quit){ if(packet_queue_get(&video_queue, &pkt, 1) < 0) { return -1; } pts = 0; // Save global pts to be stored in pFrame in first call global_video_pkt_pts = pkt.pts; avcodec_decode_video2(pVideoCodecCtx,pVideoFrame,&frame_finished,&pkt); if(pkt.dts == AV_NOPTS_VALUE && pVideoFrame->opaque && *(uint64_t*)pVideoFrame->opaque != AV_NOPTS_VALUE){ pts = *(uint64_t *)pVideoFrame->opaque; } else if(pkt.dts!=AV_NOPTS_VALUE){ pts=pkt.dts; }else{ pts=0; } pts*=av_q2d(pVideoCodecCtx->time_base); av_free_packet(&pkt); if(video_queue.nb_packets>= 100 || audio_queue.nb_packets>= 200){ //改写 printf("%d---%d\n",video_queue.nb_packets,audio_queue.nb_packets); Sleep(20); // change 10000 } else if(video_queue.nb_packets>= 50 || audio_queue.nb_packets>= 100) { Sleep(10); } if(frame_finished){ SDL_LockMutex(play->pictq_mutex); while(play->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !quit){ SDL_CondWait(play->pictq_cond,play->pictq_mutex); } SDL_UnlockMutex(play->pictq_mutex); if(quit) goto out; vp=&play->pictq[play->pictq_windex]; SDL_Overlay *bmp=vp->bmp; //定义 //序列化帧 pts=sync_video(&g_play_info,pVideoFrame,pts); vp->pts=pts; printf("video-pts:%f\n",pts); SDL_LockYUVOverlay(bmp); //加锁 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]; sws_scale(play->video_conv,pVideoFrame->data,pVideoFrame->linesize,0, pVideoFrame->height,pict.data,pict.linesize); SDL_UnlockYUVOverlay(bmp); play->pictq_windex=(play->pictq_windex+1)%VIDEO_PICTURE_QUEUE_SIZE; SDL_LockMutex(play->pictq_mutex); play->pictq_size++; SDL_UnlockMutex(play->pictq_mutex); } } out: av_free(pVideoFrame); printf("---- exit video_decode_callback ----\n"); return 0; } void video_image_display(struct play_info_t *play) { double delay,time; VideoPicture *vp; double diff,sync_threshold; if(play->pictq_size>0){ vp=&play->pictq[play->pictq_rindex]; delay=vp->pts - play->last_frame_pts; if(delay>0 && delay<10){ play->last_frame_delay=delay; } delay=play->last_frame_delay; diff=vp->pts-get_audio_clock(); sync_threshold = FFMAX(0.01, delay); if (fabs(diff) < 10) { if (diff <= -sync_threshold) delay = 0; else if (diff >= sync_threshold) delay = 2 * delay; } time=av_gettime()/1000000.0; if(time < play->frame_timer+delay) return ; if(delay>0) play->frame_timer+=delay; play->last_frame_pts=vp->pts; SDL_DisplayYUVOverlay(vp->bmp,&rect); //显示 play->pictq_rindex=(play->pictq_rindex+1)%VIDEO_PICTURE_QUEUE_SIZE; SDL_LockMutex(play->pictq_mutex); play->pictq_size--; SDL_CondSignal(play->pictq_cond); SDL_UnlockMutex(play->pictq_mutex); } } int SDLCALL video_show_callback(void* arg) { double delay=80;
struct play_info_t *play=(struct play_info_t*)arg; while(!quit){ video_image_display(play); Sleep(delay); } printf("----exit video_show_callback ----\n"); return 0; } void player_log_callback(void* ptr, int level, const char* fmt, va_list vl) { static int print_prefix = 0; static int count; char line[1024]; av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix); printf("%s",line); } int main(int argc,char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)<0){ printf("Init SDL err!\n"); return -1; } av_log_set_callback(player_log_callback); av_register_all(); AVFormatContext *pFormatCtx; pFormatCtx=avformat_alloc_context(); argv[1]="test.mp4";
if(avformat_open_input(&pFormatCtx,argv[1],NULL,NULL)<0){ printf("avformat_open_input err!\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL)<0){ printf("avformat_find_stream_info err!\n"); return -1; } //av_dump_format(pFormatCtx,0,argv[1],0); int video_stream=-1,audio_stream=-1,i; for(i=0;i<pFormatCtx->nb_streams;i++){ if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){ video_stream=i; } if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){ audio_stream=i; } } if(video_stream==-1 || audio_stream==-1){ printf("not find video or audio stream!\n"); avformat_close_input(&pFormatCtx); return -1; } g_play_info.pVideoCodecCtx=pFormatCtx->streams[video_stream]->codec; g_play_info.pAudioCodecCtx=pFormatCtx->streams[audio_stream]->codec; //find the decoder AVCodec *pVideoCodec=avcodec_find_decoder(g_play_info.pVideoCodecCtx->codec_id); if(pVideoCodec==NULL){ printf("not find video decoder!\n"); avformat_close_input(&pFormatCtx); return -1; } AVCodec *pAudioCodec=avcodec_find_decoder(g_play_info.pAudioCodecCtx->codec_id); if(pVideoCodec==NULL){ printf("not find audio decoder!\n"); avformat_close_input(&pFormatCtx); return -1; } //open the codec if(avcodec_open(g_play_info.pVideoCodecCtx,pVideoCodec)<0){ printf("can't open the video decoder!\n"); avformat_close_input(&pFormatCtx); return -1; } if(avcodec_open(g_play_info.pAudioCodecCtx,pAudioCodec)<0){ printf("can't open the audio decoder!\n"); avcodec_close(g_play_info.pVideoCodecCtx); avformat_close_input(&pFormatCtx); return -1; } //setup SDL screen = SDL_SetVideoMode(g_play_info.pVideoCodecCtx->width, g_play_info.pVideoCodecCtx->height, 0, 0);//设置视频模式参数 picture_queque_init(&g_play_info); packet_queue_init(&video_queue); packet_queue_init(&audio_queue); rect.x=0; rect.y=0; rect.w = g_play_info.pVideoCodecCtx->width; rect.h = g_play_info.pVideoCodecCtx->height; //setup the sdl audio SDL_AudioSpec sdl_audio; int64_t wanted_channel_layout = 0; int wanted_nb_channels; wanted_channel_layout = (g_play_info.pAudioCodecCtx->channel_layout && g_play_info.pAudioCodecCtx->channels == av_get_channel_layout_nb_channels(g_play_info.pAudioCodecCtx->channel_layout)) ? g_play_info.pAudioCodecCtx->channel_layout : av_get_default_channel_layout(g_play_info.pAudioCodecCtx->channels); wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX; wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout); /* SDL only supports 1, 2, 4 or 6 channels at the moment, so we have to make sure not to request anything else. */ while (wanted_nb_channels > 0 && (wanted_nb_channels == 3 || wanted_nb_channels == 5 || wanted_nb_channels > 6)) { wanted_nb_channels--; wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels); } sdl_audio.channels=av_get_channel_layout_nb_channels(wanted_channel_layout); g_play_info.pAudioCodecCtx->channels=sdl_audio.channels; g_play_info.pAudioCodecCtx->channel_layout=wanted_channel_layout; g_play_info.audio_conv=swr_alloc_set_opts(NULL,wanted_channel_layout,AV_SAMPLE_FMT_S16,g_play_info.pAudioCodecCtx->sample_rate, wanted_channel_layout,g_play_info.pAudioCodecCtx->sample_fmt, g_play_info.pAudioCodecCtx->sample_rate,0,NULL); swr_init(g_play_info.audio_conv); sdl_audio.freq=g_play_info.pAudioCodecCtx->sample_rate; sdl_audio.format=AUDIO_S16SYS; sdl_audio.channels=g_play_info.pAudioCodecCtx->channels; sdl_audio.silence=0; sdl_audio.samples=1024; sdl_audio.callback=audio_callback;//calls when the audio device need more data sdl_audio.userdata=&g_play_info; if(SDL_OpenAudio(&sdl_audio,NULL)<0){ printf("can't open audio device!\n"); goto err_alloc_frame1; } g_play_info.vedio_clock=0; g_play_info.audio_clock=0; g_play_info.audio_current_pts=0; g_play_info.last_frame_pts=0; g_play_info.last_frame_delay=0; g_play_info.frame_timer=av_gettime()/1000000.0; g_play_info.pVideoCodecCtx->get_buffer=our_get_buffer; g_play_info.pVideoCodecCtx->release_buffer=our_release_buffer; SDL_PauseAudio(0);
g_play_info.video_conv=sws_getContext(g_play_info.pVideoCodecCtx->width,g_play_info.pVideoCodecCtx->height, g_play_info.pVideoCodecCtx->pix_fmt, g_play_info.pVideoCodecCtx->width,g_play_info.pVideoCodecCtx->height,PIX_FMT_YUYV422,SWS_POINT,NULL,NULL,NULL); SDL_Thread * video_decode_thread=SDL_CreateThread(video_decode_callback,(void*)&g_play_info);
SDL_Thread * video_show_thread=SDL_CreateThread(video_show_callback,(void*)&g_play_info); SDL_Event sdl_event; AVPacket packet; while(!quit){ SDL_PollEvent(&sdl_event); if(sdl_event.type==SDL_QUIT){ SDL_CondSignal(video_queue.cond); SDL_CondSignal(audio_queue.cond); quit=1; break; } if(av_read_frame(pFormatCtx,&packet)<0){ quit=1; continue; } if(packet.stream_ndex==video_stream){ packet_queue_put(&video_queue,&packet); //change Sleep(25);//change }else if(packet.stream_index==audio_stream){ packet_queue_put(&audio_queue,&packet); //change }else{ av_free_packet(&packet); } //usleep(5000); } SDL_CloseAudio(); SDL_CondSignal(g_play_info.pictq_cond); SDL_WaitThread(video_decode_thread,NULL); SDL_WaitThread(video_show_thread,NULL); picture_queque_destroy(&g_play_info); swr_free(&g_play_info.audio_conv); sws_freeContext(g_play_info.video_conv); SDL_FreeSurface(screen); avcodec_close(g_play_info.pVideoCodecCtx); avcodec_close(g_play_info.pAudioCodecCtx); avformat_close_input(&pFormatCtx); return 0; err_alloc_frame1: avcodec_close(g_play_info.pAudioCodecCtx); avcodec_close(g_play_info.pVideoCodecCtx); avformat_close_input(&pFormatCtx); return -1; }