主机环境:Windows XP
SDL版本:SDL2-2.0.3
ffmpeg版本:ffmpeg.2.4
ffmpeg库版本:ffmpeg-20140916-git-b76d613-win32-dev.7z、ffmpeg-20140916-git-b76d613-win32-shared.7z
开发环境:CodeBlocks13.12
结合之前的音频播放器和视频播放器代码,如下
#include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/file.h> #include <libavutil/mem.h> #include <libswscale/swscale.h> #include <libavutil/channel_layout.h> #include <libavutil/samplefmt.h> #include <libswresample/swresample.h> #include <libavutil/opt.h> #include <SDL2/SDL.h> #include <stdbool.h> #if SDL_BYTEORDER == SDL_BIG_ENDIAN #define rmask 0xff000000 #define gmask 0x00ff0000 #define bmask 0x0000ff00 #define amask 0x000000ff #else #define rmask 0x000000ff #define gmask 0x0000ff00 #define bmask 0x00ff0000 #define amask 0xff000000 #endif #define AVCODEC_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; struct SwrContext *audio_swr_ctx;//重采样上下文 uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//输出声道布局 int out_nb_samples = 1024; enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输出源采样格式 int out_rate = 44100;//输出源的采样率 int out_nb_channels = 0;//得到通道数 int out_buffer_size = 0;//输出源的缓存大小 void packet_queue_init(PacketQueue *pq) { memset(pq,0,sizeof(PacketQueue));//清空结构体 pq->mutex = SDL_CreateMutex();//创建一个互斥量 pq->cond = SDL_CreateCond();//创建一个条件量 } /** first_pkt指针指向第一个数据包元素,每次更新last_pkt指针,指向最后一个数据包元素 **/ int packet_queue_put(PacketQueue *pq, AVPacket *pkt) { AVPacketList *pktlist; if(av_dup_packet(pkt) < 0) { printf("dup packet error!\n"); //将数据拷贝至私有缓存中 return -1; } pktlist = av_malloc(sizeof(AVPacketList)); if(!pktlist) { return -1; } pktlist->pkt = *pkt;//初始化列表元素 pktlist->next = NULL;//初始化列表元素 SDL_LockMutex(pq->mutex);//上锁 if(!pq->last_pkt) { //如果last_pkt指针为空,则重新链接first_pkt指针至新建的pktlist元素 pq->first_pkt = pktlist; } else { //如果last指针不为空,则将pktlist元素添加到last链表末端 pq->last_pkt->next = pktlist; } pq->last_pkt = pktlist;//将last_pkt指针指向新添加的pktlist元素,移动last_pkt指针 pq->nb_packets++;//数据包个数增加 pq->size+=pktlist->pkt.size;//数据大小增加 SDL_CondSignal(pq->cond);//通知条件变量 SDL_UnlockMutex(pq->mutex);//解锁 return 0; } int quit = 0; /** 从队列中取出一个数据包block:标记是否以阻塞方式取数据包 **/ static int packet_queue_get(PacketQueue *pq, AVPacket *ptk, int block) { AVPacketList *pktlist; int ret; SDL_LockMutex(pq->mutex);//上锁 static bool flag = false; while(1) { if(quit) { ret = -1; break; } pktlist = pq->first_pkt;//从第一个数据包开始提取 if(pktlist) { //如果该数据包存在的话 pq->first_pkt = pktlist->next;//移动队列的first指针到下一个数据包 if(!pq->first_pkt) { //如果first指针指向的数据包为空,则清除last数据包指针(说明队列里已经没有数据包了) pq->last_pkt = NULL; } pq->nb_packets --;//数据包个数自减 pq->size -= pktlist->pkt.size;//数据大小自减 *ptk = pktlist->pkt;//更新数据包 av_free(pktlist);//释放数据包列表 ret = 1; flag = true; break; } else if(!block) { //如果是非阻塞方式直接返回 ret = 0; if(flag) ret = 2; break; } else { //如果是阻塞方式则进入睡眠模式等待唤醒 SDL_CondWait(pq->cond,pq->mutex);//等待信号变量 } } SDL_UnlockMutex(pq->mutex);//解锁 return ret; } /* 解码一个音频数据包,返回解码的数据大小,一个音频包可能包含多个音频帧,但一次只解码一个音频帧, 所以一包音频可能需要多次才能解码完,使用while语句判断包数据是否全部解完,如果没有就解码当前包中的帧 ,修改状态参数,否则,释放数据包,再从队列中取数据,记录初始值,再进循环。 */ int audio_decode_frame(AVCodecContext *aCodecCtx,uint8_t *out_buffer,AVFrame *frame) { static AVPacket pkt;//音频数据包,从队列中取出的数据包存放在这里 static int audio_pkt_size = 0;//音频包数据大小 int len1; int got_frame; int ret,result,out_sample_size;//输出的解码样品大小 for(;;) { while(audio_pkt_size > 0) { //音频包数据还未解码完,继续执行解码操作 out_sample_size = pkt.size; got_frame = 0; len1 = avcodec_decode_audio4(aCodecCtx,frame,&got_frame,&pkt);//解码音频,从packet到frame中 if(len1 < 0) { //如果有错误发生 audio_pkt_size = 0; break; } if(got_frame>0) { //得到解码帧后执行重采样输出至out_buffer中 ret = swr_convert(audio_swr_ctx,&out_buffer,192000,(const uint8_t **)frame->data,frame->nb_samples); if(ret < 0) { //转换失败 return -1; } out_sample_size = av_samples_get_buffer_size(NULL,out_nb_channels,ret,out_sample_fmt,1); //对于sample.wav来说ret=frame->nb_samples } if(out_sample_size<=0) continue; pkt.data += len1;//移动未解码数据指针 pkt.size -= len1;//更新未解码数据大小 audio_pkt_size -= len1;//更新未解码数据大小 return out_sample_size;//解码完成返回解码的数据 } if(pkt.data) av_free_packet(&pkt); memset(&pkt,0,sizeof(pkt)); if(quit) return -1; result = packet_queue_get(&audioQ,&pkt,0); if(result<0)//从队列中取出一个音频数据包 从这里展开!!! return -1;//读取失败 else if(result == 2) return -2; audio_pkt_size = pkt.size;//得到音频数据包的大小 } } void audio_callback(void *userdata, Uint8 *stream, int len) { AVCodecContext *aCodecCtx = (AVCodecContext*)userdata; int len1, audio_size=0; 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;//已输出音频数据大小 AVFrame *frame; frame = av_frame_alloc(); if(frame == NULL) { printf("alloc frame failed!\n"); av_frame_free(&frame); return; } while(len > 0) { if(audio_buf_index >= audio_buf_size)//音频已播放完需要更多的数据 { audio_size = audio_decode_frame(aCodecCtx,audio_buf,frame);//获取解码的数据大小,数据在frame中一个帧 if(audio_size < 0) { //解码失败,则播放静音缓存区写0即可 if(audio_size== -2) { SDL_PauseAudio(1); return; } audio_buf_size = 1024; memset(stream,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;//如果帧大小比SDL缓存区还要大,则分割播放 SDL_memcpy(stream,(uint8_t *)audio_buf + audio_buf_index,len1); len -= len1; stream += len1; audio_buf_index += len1;//更新已输出音频数据大小 } av_frame_free(&frame); } int main(int argc, char* args[]) { av_register_all(); AVFormatContext* pFormatCtx; if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0) { printf("SDL init failed error:%s\n",SDL_GetError()); return -1; } pFormatCtx = avformat_alloc_context(); if(pFormatCtx == NULL) { printf("couldn't alloc a avformatcontext\n"); return -1; } if(avformat_open_input(&pFormatCtx,"123.flv",NULL, NULL) != 0) { printf("couldn't open file!\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL) != 0) { printf("couldn't find stream information!\n"); return -1; } printf("nb_streams:%d\n",pFormatCtx->nb_streams); int i = 0,videoStream = -1,audioStream = -1; AVCodecContext* pCodecCtx,*aCodecCtx;//视频、音频 //一个文件一般有两种流:一个音频流、一个视频流 for(i = 0; i < pFormatCtx->nb_streams; i++) { printf("nb_streams[%d]:%d\n",i,pFormatCtx->streams[i]->codec->codec_type); 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) { printf("couldn't find a video stream!\n"); return -1; } if(audioStream == -1) { printf("couldn't find a audio stream!\n"); return -1; } //here we find a video stream and a audio stream pCodecCtx = pFormatCtx->streams[videoStream]->codec; aCodecCtx = pFormatCtx->streams[audioStream]->codec; AVCodec* pCodec,*aCodec;//视频、音频编码器 //find the decoder for the video stream pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL) { printf("unsupported codec!\n"); return -1; } //打开视频解码器 if(avcodec_open2(pCodecCtx,pCodec,NULL) < 0) { printf("open the codec failed!\n"); return -1; } //查找音频解码器 aCodec = avcodec_find_decoder(aCodecCtx->codec_id); if(aCodec == NULL) { printf("unsupported audio codec!\n"); return -1; } //打开音频解码器 if(avcodec_open2(aCodecCtx,aCodec,NULL) < 0) { printf("open the audio codec failed!\n"); return -1; } packet_queue_init(&audioQ); //SDL_PauseAudio(0);//开始播放 //===================================前期工作完成====================================== SDL_Surface *screen = NULL; SDL_Window* gWindow = NULL; SDL_Surface*surface = NULL; gWindow = SDL_CreateWindow( "XPlayer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_SHOWN ); if(gWindow == NULL) { printf("create window failed:%s",SDL_GetError()); return -1; } screen = SDL_GetWindowSurface(gWindow); if(screen == NULL) { printf("create surface failed:%s",SDL_GetError()); return -1; } SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,0xff,0xff,0xff));//填充白色 AVFrame* pFrame,*pFrameRGB;//用于保存一个帧(保存成24位RGB色的PPM文件,需要格式转换,将原始的帧切换成特定格式的帧) pFrame = av_frame_alloc();//为帧申请一块内存 pFrameRGB = av_frame_alloc();//为帧申请一块内存 if(pFrameRGB == NULL) { printf("alloc rgb frame failed!\n"); return -1; } uint8_t *buffer; int numBytes; numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);//转成rgb24需要的帧大小 buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));//为rgb24帧申请空间,buffer为起始地址 avpicture_fill((AVPicture*)pFrameRGB,buffer,AV_PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height); //把申请的内存和rgb24帧结合起来,准备读取数据 int frameFinished,ret; AVPacket packet; struct SwsContext *pSwsCtx; pSwsCtx = sws_getCachedContext(NULL,pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height, AV_PIX_FMT_RGB24,SWS_BILINEAR,NULL,NULL,NULL); if(pSwsCtx == NULL) { printf("get sws context failed!\n"); return -1; } int64_t in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels); audio_swr_ctx = swr_alloc(); if(!audio_swr_ctx) { printf("could not allocate resampler context!\n"); exit(1); } out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);//得到输出源通道数 printf("in_channel_layout:%s\n",av_get_channel_name(in_channel_layout)); printf("out_channel_layout:%s\n",av_get_channel_name(out_ch_layout)); printf("in_channel_number:%d\n",aCodecCtx->channels); printf("out_channel_number:%d\n",out_nb_channels); printf("in_sample_fmt:%s\n",av_get_sample_fmt_name(aCodecCtx->sample_fmt)); printf("out_sample_fmt:%s\n",av_get_sample_fmt_name(out_sample_fmt)); printf("in_sample_rate:%d\n",aCodecCtx->sample_rate); out_buffer_size = av_samples_get_buffer_size(NULL,out_nb_channels,out_nb_samples,out_sample_fmt,1); av_opt_set_int(audio_swr_ctx,"in_channel_layout",in_channel_layout,0); av_opt_set_int(audio_swr_ctx,"in_sample_rate",aCodecCtx->sample_rate,0); av_opt_set_sample_fmt(audio_swr_ctx,"in_sample_fmt",aCodecCtx->sample_fmt,0); av_opt_set_int(audio_swr_ctx,"out_channel_layout",out_ch_layout,0); av_opt_set_int(audio_swr_ctx,"out_sample_rate",out_rate,0); av_opt_set_sample_fmt(audio_swr_ctx,"out_sample_fmt",out_sample_fmt,0); ret = swr_init(audio_swr_ctx);//初始化重采样结构体 if(ret < 0) { printf("failed to init the resampling context!\n"); exit(1); } SDL_AudioSpec wanted_spec,obtained_spec;//前者是我们想要的配置,后者是我们实际用的配置 wanted_spec.freq = out_rate;//输出源的采样率 wanted_spec.format = AUDIO_S16SYS;//音频格式 wanted_spec.channels = out_nb_channels;//输出源的通道数 wanted_spec.silence = 0; wanted_spec.samples = out_nb_samples;//缓存区大小1024 wanted_spec.callback = audio_callback; wanted_spec.userdata = aCodecCtx; if(SDL_OpenAudio(&wanted_spec,&obtained_spec) < 0) { printf("open audio failed. sdl error:%s",SDL_GetError()); return -1; } SDL_PauseAudio(0);//开始播放 bool sdl_quit = false; SDL_Event e; while(av_read_frame(pFormatCtx, &packet)>=0) //读取一个包并保存到packet中 { // Is this a packet from the video stream? if(packet.stream_index==videoStream)//确保是视频流的数据包 { // Decode video frame ret = avcodec_decode_video2(pCodecCtx, pFrame,&frameFinished, &packet);//解码数据包 if(ret < 0) { printf("Decode failed!\n"); return -1; } // Did we get a video frame? if(frameFinished) { sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); surface = SDL_CreateRGBSurfaceFrom(pFrameRGB->data[0],pCodecCtx->width,pCodecCtx->height,24,pCodecCtx->width*3, rmask,gmask,bmask,amask); SDL_BlitSurface(surface,NULL,screen,NULL); SDL_UpdateWindowSurface(gWindow); SDL_Delay(50); } } else if(packet.stream_index == audioStream) { //音频包处理 packet_queue_put(&audioQ,&packet); } // Free the packet that was allocated by av_read_frame else av_free_packet(&packet); if(SDL_PollEvent(&e) != 0) { if(e.type == SDL_QUIT) { sdl_quit = true;//exit } } if(sdl_quit) { break; } SDL_Delay(1); } if(!sdl_quit) { while((SDL_GetAudioStatus() == SDL_AUDIO_PLAYING)) SDL_Delay(1000); } printf("======stop playing audio======\n"); SDL_CloseAudio(); // Free the RGB image av_free(buffer); av_free(pFrameRGB); swr_free(&audio_swr_ctx); sws_freeContext(pSwsCtx); SDL_FreeSurface(surface); surface = NULL; SDL_DestroyWindow(gWindow); gWindow = NULL; SDL_Quit(); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); avcodec_close(aCodecCtx); // Close the video file avformat_close_input(&pFormatCtx); return 0; }
声音和视频都可以播放了,其中SDL的事件处理还不完善,当用鼠标拖动窗口或长点击窗口时音频播放就会停止,但视频不受影响,目前还未找到原因,希望大家可以帮忙解决一哈,让小弟进步。。。
ffmpeg指导教程:http://dranger.com/ffmpeg/ffmpeg.html
工程代码:http://download.csdn.net/detail/key123zhangxing/8015895