基于ffmpeg网络播放器的教程与总结.docx_第1页
基于ffmpeg网络播放器的教程与总结.docx_第2页
基于ffmpeg网络播放器的教程与总结.docx_第3页
基于ffmpeg网络播放器的教程与总结.docx_第4页
基于ffmpeg网络播放器的教程与总结.docx_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

基于ffmpeg网络播放器的教程与总结一、 概述为了解决在线无广告播放youku网上的视频。(youku把每个视频切换成若干个小视频)。视频资源解析可以从网上获取,此网站根据你输入的优酷的播放网页地址解析成若干个真实的视频地址。二、 实现首先搜索关闭网络播放器(流媒体播放器的实现方法)得出的结论,目前主流的播放器分三大阵营微软,苹果,基于FFmpeg内核的。所以我决定从ffmpeg开源的播放器入手。最出名的ffmpeg播放器vcl播放器,开源免费。最后选择放弃。原因1 依赖于vcl的68M的plugins和libvlccore.dll,libvlc.dll项目生成文件过大。2即使这样不能解决播放多段视频卡顿现象。最后决定使用ffmpeg官方的ffpaly播放器只有1000多行 (很激动),使用ffmpeg编解码,使用sdl做显示。本想只修改下就行了。结果发现里面代码结构过于复杂,搞懂每行很是吃力。而且是用sdl做显示,sdl需要句柄。而我这个是为wpf项目量身定做的。Wpf只有顶层窗口有句柄。如果是使用wpf嵌入winform控件。导致此winform控件只能最上层显示(原因是wpf是directui思想实现的)。所以也放弃了。决定使用ffmpeg库,自己开发查看网上关于ffmpeg开发的总结。对ffmpeg开发有个总体方向。首先我们先把视频搞出来,参考网上 100行代码搞定视频。然后100行搞定音频网上这样视频音频都已经搞出来了。但是我们怎么把视频音频一起搞出来呢?Csdn有一份文档网上此文档介绍了用ffmpeg开发视频播放器的详细方法,有注解。但是已经过时了。最新的代码在网上但是文档中的思想还是挺受用的。代码不同,思想是通的。结论,视频包含视频流,音频流,字幕流(一般没有),音视频同步跟进播放时间戳pts来做的。 视频和音频得出pts的方式有所不同。具体看文档。如果按文档的注释,然后根据github的代码,编译我们发现视频可以显示,音频出现乌拉乌拉的杂音。 此时我参考100行搞定音频网上源码修改了github的音频部分。调试运行,可以播放了。至此 我们的视频播放器可以播放了 ,使用sdl做显示。那现在我们还是没解决问题。网络播放器,多段无卡顿。在此基础上我们分析,可以开辟一个线程从网络上下载视频,音频,放入到缓冲队列。音视频播放线程从缓冲区读取数据解析。这就是网络播放器的原理,而且不会卡顿。其中音视频同步用音频驱动视频的方式实现。显示目前暂用sdl。经过上面这些,我们的网络播放器终于可以工作了。那现在只剩下一个wpf句柄问题了。好在我看到了网上文章里面介绍了vlc播放器c#开源代码,可以使用共享内存。但是说的不够详细这两个开源项目都是用共享内存实现的。 参考此两篇文章。我的播放器终于可以播放网络的视频,音频,然后才wpf播放了。中间有wpf调用c方法的一些细节。至此我们的问题真的解决了吗?NO,因为我们回调函数调用共享内存显示,里面有很多问题,比如当我们关闭程序时会出现访问锁定内存等问题。此问题肯定是可以解决的。但是我们东拼西凑把问题解决了。 当此方案不是最好的。这3篇文章介绍了怎么使用mediaelement完美解决播放视频问题。播放器源码可以用网上下面是我的播放器c部分的代码/*本播放器主要是解决 从优酷上播放视频。 是有多段网络视频组成一个完整视频。解决方案,开辟两个线程,一个线程从网络中读取数据包放入缓冲池(视频缓冲池和音频缓冲池)一个线程从音频缓冲池读取数据播放。一个从视频缓冲池中读取播放.难点1:av_read_frame是读取packet(包) 数据, 几包数据 组成avframe(帧)音频帧转换成byte 存储起来 放入缓冲池 吃音频byte可以直接放入音频流中播放视频帧也是byte 存储起来,此视频byte数组可以转换为图片 PIX_FMT_RGB24为了同步音视频,我们把没帧的最后一包的pts记录下来放入缓冲区*/#include stdafx.h#include BonkerPlayer.h#include #include #include externC#include libavcodec/avcodec.h#include libavformat/avformat.h#include libswresample/swresample.h#include libswscale/swscale.h#include /SDL#include sdl/SDL.h#include sdl/SDL_thread.h;#define VideoBufferMaxSize 80/2048 /视频缓冲区最大值,大于此值 则不下载数据#define VideoBufferMinSize 20/1024 /视频缓冲区最小值,小于此值,则唤醒下载#define AudioBufferMaxSize 80/2048 /音频缓冲区最大值,大于此值 则不下载数据#define AudioBufferMinSize 20/1024 /音频缓冲区最小值,小于此值,则唤醒下载#define SDL_AUDIO_BUFFER_SIZE 1024 /音频流的缓冲区/#define VideoType PIX_FMT_YUV420P /视频转换的格式#define VideoType PIX_FMT_BGR24 /视频转换的格式#define MAX_AUDIO_FRAME_SIZE 192000 / 1 second of 48khz 32bit audio/static char ErrorMsg100=;/错误的提示信息intFileDuration=0;/视频的长度 单位秒intflag=100;/标识 播放,暂停,退出 0退出,1标识,2暂停/声明了函数指针DispalyVideoDele Fn=NULL;Uint32 audio_len;Uint8 *audio_pos;doublecurrentAudioClock=0;/当前音频播放时间doublecurrentVideoClock=0;/当前视频播放时间doublecurrentBufferClock=0;/当前以缓冲的时间,用于缓冲进度条/double currentPlayClock=0;/当前播放的时间,用于播放进度条doublediffClock=0.2;/音视频相差的死区intCurrentVolume=SDL_MIX_MAXVOLUME/2;/当前声音的大小SDL_Thread *decodeTid=NULL;/解码线程SDL_Thread *PlayVideoTid=NULL;/视频播放线程SDL_Thread *PlayAudioTid=NULL;/音频播放线程/快进的参数boolisSeek=false;/是否在快进intglobal_seek_index=0;/文件索引 快进doublegloble_seek_pos=0;/快进的地方/存储音频的队列typedefstructAudioItemUint8 *AudioData;/音频数据intLength;/音频长度doublePts;/时间戳AudioItem *Next;/尾部SDL_AudioSpec *wanted_spec;AudioQueueItem;typedefstructAudioQueueItem *FirstItem;/队列头AudioQueueItem *LastItem;/队列位intLength;/队列长度SDL_mutex *audioMutex;/用于同步两个线程同时操作队列的 互斥量SDL_cond *audioCond;/唤醒线程AudioQueue;/存储视频的队列typedefstructVideoItemUint8 *VideoData;/音频数据intWidth;/视频图片的宽度intHeight;/视频图片的高度intLength;/视频长度doublePts;/时间戳VideoItem *Next;/尾部VideoQueueItem;typedefstructVideoQueueItem *FirstItem;/队列头VideoQueueItem *LastItem;/队列位intLength;/队列长度doubleBufferPts;/缓冲的ptsSDL_mutex *videoMutex;/用于同步两个线程同时操作队列的 互斥量SDL_cond *videoCond;/唤醒线程VideoQueue;VideoQueue *videoQueue=NULL;/视频队列AudioQueue *audioQueue=NULL;/音频队列/清空视频队列voidVideoQueueClear(VideoQueue *vq)VideoItem *item,*temp;SDL_LockMutex(vq-videoMutex);for(item=vq-FirstItem; item!=NULL; item=temp)temp=item-Next;/av_free(item-VideoData);/释放video里面的数据av_free(item);vq-Length-;vq-FirstItem=NULL;vq-LastItem=NULL;SDL_UnlockMutex(vq-videoMutex);/清空音频队列voidAudioQueueClear(AudioQueue *aq)AudioItem *item,*temp;SDL_LockMutex(aq-audioMutex);for(item=aq-FirstItem; item!=NULL; item=temp)temp=item-Next;/av_free(item-AudioData);/释放video里面的数据av_free(item-wanted_spec);av_free(item);aq-Length-;aq-FirstItem=NULL;aq-LastItem=NULL;SDL_UnlockMutex(aq-audioMutex);/初始化视频队列voidVideoQueueInit(VideoQueue *vq)memset(vq, 0,sizeof(VideoQueue);/初始化首地址为0vq-videoMutex=SDL_CreateMutex();vq-videoCond=SDL_CreateCond();/初始化音频队列voidAudioQueueInit(AudioQueue *aq)memset(aq,0,sizeof(AudioQueue);aq-audioMutex=SDL_CreateMutex();aq-audioCond=SDL_CreateCond();/向队列添加数据intVideoQueuePut(VideoQueue *vq,VideoQueueItem *item)intresult=0;SDL_LockMutex(vq-videoMutex);/加锁if(vq-LengthFirstItem)/第一个item为null 则队列是空的vq-FirstItem=item;vq-LastItem=item;vq-Length=1;vq-BufferPts=item-Pts;elsevq-LastItem-Next=item;/添加到队列后面vq-Length+;vq-LastItem=item;/此item变成队列尾部vq-BufferPts=item-Pts;if(vq-Length=VideoBufferMinSize)SDL_CondSignal(vq-videoCond);/唤醒其他线程 如果缓冲区里面有几个数据后再唤醒 较好result=1;elseSDL_CondWait(vq-videoCond,vq-videoMutex);/解锁 等待被唤醒SDL_UnlockMutex(vq-videoMutex);/解锁returnresult;/向队列中取出数据,放入item中intVideoQueueGet(VideoQueue *vq,VideoQueueItem *item)intresult=0;SDL_LockMutex(vq-videoMutex);if(vq-Length0)if(vq-FirstItem)/有数据*item=*(vq-FirstItem);if(!vq-FirstItem-Next)/只有一个vq-FirstItem=NULL;vq-LastItem=NULL;elsevq-FirstItem=vq-FirstItem-Next;vq-Length-;item-Next=NULL;result= 1;if(vq-LengthvideoCond);/唤醒下载线程elseSDL_CondWait(vq-videoCond,vq-videoMutex);/解锁 等待被唤醒SDL_UnlockMutex(vq-videoMutex);returnresult;/向队列添加数据intAudioQueuePut(AudioQueue *aq,AudioQueueItem *item)intresult=0;SDL_LockMutex(aq-audioMutex);/加锁if(aq-LengthFirstItem)/第一个item为null 则队列是空的aq-FirstItem=item;aq-LastItem=item;aq-Length=1;elseaq-LastItem-Next=item;/添加到队列后面aq-Length+;aq-LastItem=item;/此item变成队列尾部if(aq-Length=AudioBufferMinSize)SDL_CondSignal(aq-audioCond);/唤醒其他线程 如果缓冲区里面有几个数据后再唤醒 较好result=1;else/音频缓冲区的大小 大于设定值 则让线程等待SDL_CondWait(aq-audioCond,aq-audioMutex);/解锁 等待被唤醒SDL_UnlockMutex(aq-audioMutex);/解锁returnresult;/向队列中取出数据,放入item中intAudioQueueGet(AudioQueue *aq,AudioQueueItem *item)intresult=0;SDL_LockMutex(aq-audioMutex);if(aq-Length0)if(aq-FirstItem)/有数据*item=*(aq-FirstItem);if(!aq-FirstItem-Next)/只有一个aq-FirstItem=NULL;aq-LastItem=NULL;elseaq-FirstItem=aq-FirstItem-Next;aq-Length-;item-Next=NULL;result=1;if(aq-LengthaudioCond);/唤醒下载线程elseSDL_CondWait(aq-audioCond,aq-audioMutex);/解锁 等待被唤醒SDL_UnlockMutex(aq-audioMutex);returnresult;/输出声音的回调函数voidAudioCallback(void*udata,Uint8 *stream,intlen)/SDL 2.0SDL_memset(stream, 0, len);if(audio_len=0)/* Only play if we have data left */return;len=(lenaudio_len?audio_len:len);/* Mix as much data as possible */SDL_MixAudio(stream,audio_pos,len,CurrentVolume);audio_pos += len;audio_len -= len;/下载视频和音频流并 解码 并放入相应的队列中intDecodePacket(void*arg)VideoState *vs=(VideoState *)arg;intlength=vs-Length;doublecurrentAllFilePts=0;av_register_all();/注册所有解码器avformat_network_init();/初始化流媒体格式for(intj = 0; j Urlsj;AVFormatContext *pFormatCtx;pFormatCtx = avformat_alloc_context();/打卡文件if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)/strcpy(ErrorMsg,无法打开网络流);return-1;/avformat_close_inputif(av_find_stream_info(pFormatCtx)duration/1000000;/把一个文件拆分为视频流和音频流intvideoIndex=-1,audioIndex=-1;inti=0;/获取音频流和视频流的索引for(i=0; inb_streams; i+)if(pFormatCtx-streamsi-codec-codec_type=AVMEDIA_TYPE_VIDEO)videoIndex=i;elseif(pFormatCtx-streamsi-codec-codec_type=AVMEDIA_TYPE_AUDIO)audioIndex=i;AVCodecContext *pCodecCtx,*aCodecCtx;/视频,音频的解码器上下文AVCodec *pCodec,*aCodec;/视频,音频解码器if(videoIndex!=-1)/视频解码器上下文,pCodecCtx=pFormatCtx-streamsvideoIndex-codec;pCodec=avcodec_find_decoder(pCodecCtx-codec_id);elseif(audioIndex!=-1)/音频解码器上下文aCodecCtx=pFormatCtx-streamsaudioIndex-codec;aCodec=avcodec_find_decoder(aCodecCtx-codec_id);elseif(videoIndex!=-1)/打开解码器if(avcodec_open2(pCodecCtx, pCodec,NULL)0)/strcpy(ErrorMsg,无法打开视频解码器);return-1;elseif(audioIndex!=-1)if(avcodec_open2(aCodecCtx, aCodec,NULL)width, pCodecCtx-height);img_convert_ctx = sws_getContext(pCodecCtx-width, pCodecCtx-height, pCodecCtx-pix_fmt, pCodecCtx-width, pCodecCtx-height, VideoType, SWS_BICUBIC, NULL, NULL, NULL);/把音频帧转化为数组的参数/uint64_t out_channel_layout=AV_CH_LAYOUT_STEREO;AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;intout_sample_rate=44100;int64_t in_channel_layout=av_get_channel_layout_nb_channels(aCodecCtx-channels);intout_channels=av_get_channel_layout_nb_channels(aCodecCtx-channels);intout_nb_samples=1024;audioLength=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1);structSwrContext *au_convert_ctx;au_convert_ctx = swr_alloc();au_convert_ctx=swr_alloc_set_opts(au_convert_ctx,aCodecCtx-channels, out_sample_fmt, out_sample_rate,in_channel_layout,aCodecCtx-sample_fmt , aCodecCtx-sample_rate,0, NULL);swr_init(au_convert_ctx);intsample=SDL_AUDIO_BUFFER_SIZE;/解码一包数据,一帧数据有多包while(flag!=0&av_read_frame(pFormatCtx, packet)=0)if(isSeek)/要快进/做快进if(j=global_seek_index)intseekFlag=avformat_seek_file(pFormatCtx, -1, (globle_seek_pos-10)* AV_TIME_BASE, globle_seek_pos * AV_TIME_BASE, (globle_seek_pos+10)* AV_TIME_BASE, AVSEEK_FLAG_ANY);if(seekFlag=0)currentAllFilePts=0;for(intk = 0; k timesk;/av_seek_frame(pFormatCtx, -1 , globle_seek_pos * AV_TIME_BASE, AVSEEK_FLAG_ANY);isSeek=false;elsej=global_seek_index-1;break;if(flag=0)/退出break;elseif(flag=1)/播放elseif(flag=2)SDL_Delay(1);continue;frameFinished=0;/视频数据包 添加到视频队列中if(packet-stream_index=videoIndex)/把数据包转换为数据帧result=avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,packet);doublepts=0;if(packet-dts = AV_NOPTS_VALUE& pFrame-opaque & *(uint64_t*)pFrame-opaque != AV_NOPTS_VALUE) pts = *(uint64_t *)pFrame-opaque;elseif(packet-dts != AV_NOPTS_VALUE) pts = packet-dts;elsepts = 0;pts *= av_q2d(pFormatCtx-streamsvideoIndex-time_base);/printf(+readVideo %dn,videoQueue-Length);if(resultwidth, pCodecCtx-height);sws_scale(img_convert_ctx, (constuint8_t*const*)pFrame-data, pFrame-linesize, 0, pCodecCtx-height, pFrameRGB-data, pFrameRGB-linesize);/创建视频itemVideoQueueItem *videoItem;videoItem=(VideoQueueItem *)av_mallocz(sizeof(VideoQueueItem);videoItem-Height=pCodecCtx-height;videoItem-Width=pCodecCtx-width;videoItem-VideoData=bufferRGB;/videoItem-Length=videoLength;/videoItem-VideoData=pFrameRGB-data0;videoItem-Length=pFrameRGB-linesize0;/获取显示时间戳ptscurrentFilePts=pts;videoItem-Pts = currentAllFilePts+currentFilePts;/音频绝对pts;videoItem-Next=NULL;/添加到队列中while(flag!=0&!VideoQueuePut(videoQueue,videoItem);/av_free(bufferRGB);/释放/音频数据包 ,添加到音频队列中elseif(packet-stream_index=audioIndex)result= avcodec_decode_audio4( aCodecCtx, pFrame,&frameFinished, packet);doublepts=0;if(packet-dts = AV_NOPTS_VALUE& pFrame-opaque & *(uint64_t*)pFrame-opaque != AV_NOPTS_VALUE) pts = *(uint64_t *)pFrame-opaque;elseif(packet-dts != AV_NOPTS_VALUE) pts = packet-dts;elsepts = 0;pts *= av_q2d(pFormatCtx-streamsvideoIndex-time_base);/p

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论