EasyPlayerPro for Windows是基于ffmpeg進(jìn)行開發(fā)的全功能播放器,開發(fā)過程中參考了很多開源的播放器,諸如vlc和ffplay等,其中最強(qiáng)大的莫過于vlc,但是鑒于vlc框架過于龐大而其中仍存在諸多問題而舍棄了,而其他的更傾向于演示demo,只能提供部分借鑒意義;故而,EasyPlayerPro 一貫秉承Easy系列小而精,接口簡(jiǎn)單功能強(qiáng)大的宗旨從新設(shè)計(jì)了一套框架,該套框架能適應(yīng)多線程調(diào)用以及多個(gè)播放實(shí)例同時(shí)運(yùn)行,和EasyPlayer一樣Easy; 當(dāng)然,在此也鄭重的感謝各大開源播放器以及ffmpeg的作者的無私奉獻(xiàn)。
EasyPlayerPro分為三大模塊:打開模塊,讀取流數(shù)據(jù)模塊,解碼模塊和渲染模塊,其中:
(1) 打開模塊 打開流模塊很簡(jiǎn)單,教科書式的調(diào)用方法:
player->avformat_context = avformat_alloc_context();player->avformat_context->interrupt_callback.callback = interrupt_cb;player->avformat_context->interrupt_callback.opaque = player;// open input file
AVDictionary *options =
NULL ;//av_dict_set(&options,
"rtsp_transport" ,
"udp" ,
0 );
if (avformat_open_input(&player->avformat_context, url, fmt, &options) !=
0 ) {goto error_handler;}// find stream info
if (avformat_find_stream_info(player->avformat_context,
NULL ) <
0 ) {goto error_handler;}// set current audio & video streamfor (i=
0 ,idx=-
1 ,cur=-
1 ; i<(int)player->avformat_context->nb_streams; i++) {
switch (
type ) {case AVMEDIA_TYPE_AUDIO :// get last codec context if (player ->acodec_context ) {lastctxt = player ->acodec_context ;} // get new acodec_context & astream_timebaseplayer->acodec_context = player->avformat_context->streams[idx]->codec;player->astream_timebase = player->avformat_context->streams[idx]->time_base;// reopen codec
if (lastctxt) avcodec_close(lastctxt);decoder = avcodec_find_decoder(player->acodec_context->codec_id);
if (decoder && avcodec_open2(player->acodec_context, decoder,
NULL ) ==
0 ) {player->astream_index = idx;}
else {av_log(
NULL ,
AV_LOG_WARNING ,
"failed to find or open decoder for audio !\n" );player->astream_index = -
1 ;}break;
case AVMEDIA_TYPE_VIDEO :// get last codec context
if (player->vcodec_context) {lastctxt = player->vcodec_context;}// get new vcodec_context & vstream_timebaseplayer->vcodec_context = player->avformat_context->streams[idx]->codec;player->vstream_timebase = player->avformat_context->streams[idx]->time_base;// reopen codec
if (lastctxt) avcodec_close(lastctxt);decoder = avcodec_find_decoder(player->vcodec_context->codec_id);
if (decoder && avcodec_open2(player->vcodec_context, decoder,
NULL ) ==
0 ) {player->vstream_index = idx;}
else {av_log(
NULL ,
AV_LOG_WARNING ,
"failed to find or open decoder for video !\n" );player->vstream_index = -
1 ;}break;
case AVMEDIA_TYPE_SUBTITLE :return -
1 ; // todo...}}
if (idx == -
1 ) return -
1 ;// for audio
if (player->astream_index != -
1 ){arate = player->acodec_context->sample_rate;aformat = player->acodec_context->sample_fmt;alayout = player->acodec_context->channel_layout;//++ fix audio channel layout issue
if (alayout ==
0 ) {alayout = av_get_default_channel_layout(player->acodec_context->channels);}//}// for video
if (player->vstream_index != -
1 ) {vrate = player->avformat_context->streams[player->vstream_index]->r_frame_rate;
if (vrate.num / vrate.den >=
100 ) {vrate.num =
25 ;vrate.den =
1 ;}player->vcodec_context->pix_fmt = vformat;width = player->vcodec_context->width;height = player->vcodec_context->height;}
首先,avformat_open_input打開一個(gè)流,為了避免在打開流的時(shí)候出現(xiàn)阻塞,我們創(chuàng)建一個(gè)線程來執(zhí)行,同時(shí),為了防止ffmpeg內(nèi)部出現(xiàn)持久行的阻塞,我們傳入阻塞回調(diào)函數(shù),在關(guān)閉流或者其他必要的時(shí)候解除阻塞;avformat_find_stream_info獲取流的解碼信息,根據(jù)音視頻以及字幕的解碼信息初始化解碼器;
(2) 讀取流數(shù)據(jù)模塊
retv
= av_read_frame(player
-> avformat_context, packet);
if (retv
< 0 ){
if (player
-> avformat_context
-> pb
&& player
-> avformat_context
-> pb
-> error){ player
-> error_flag
= 1 ;break;}player
-> player_status
|= PS_D_PAUSE;pktqueue_write_post_i(player
-> pktqueue, packet);usleep(
20 * 1000 ); continue;}player
-> error_flag
= 0 ;
if (packet
-> stream_index
== player
-> astream_index){pktqueue_write_post_a(player
-> pktqueue, packet);}
if (packet
-> stream_index
== player
-> vstream_index){pktqueue_write_post_v(player
-> pktqueue, packet);}
if ( packet
-> stream_index
!= player
-> astream_index
&& packet
-> stream_index
!= player
-> vstream_index ){av_packet_unref(packet); pktqueue_write_post_i(player
-> pktqueue, packet);}}
讀取數(shù)據(jù)模塊超級(jí)簡(jiǎn)單,創(chuàng)建一個(gè)線程循環(huán)執(zhí)行av_read_frame,讀取到一幀就將其放入隊(duì)列,這里采用了ffplay的阻塞的方式來處理隊(duì)列的消費(fèi)者和生產(chǎn)者的問題,這塊有待優(yōu)化,后續(xù)將改成無鎖循環(huán)隊(duì)列模式,如EasyPlayer。
(3) 解碼模塊 解碼模塊分為音頻和視頻解碼模塊,音視頻的解碼流程非常相似, 主要分為三步: a. 從隊(duì)列中讀取音視頻編碼數(shù)據(jù); b. 音視頻分別采用avcodec_decode_audio4和avcodec_decode_video2進(jìn)行解碼; c. 音視頻渲染; 這里著重講解視頻的解碼后的過程,其中涉及到解碼后的原始圖像數(shù)據(jù)進(jìn)行處理,解碼出一幀圖像以后,我們需要對(duì)其進(jìn)行字幕和圖像或者其他的視頻圖像的疊加,借助ffmpeg強(qiáng)大的圖像轉(zhuǎn)換和縮放能力,借助VFX庫(kù)我們很容易實(shí)現(xiàn):
consumed
= avcodec_decode_video2(player
-> vcodec_context, vframe,
& gotvideo, packet);
if (consumed
< 0 ) {av_log(
NULL , AV_LOG_WARNING,
"an error occurred during decoding video.\n" );break;}
if (gotvideo) {
#if 1 WaterMarkInfo g_waterMarkInfo
= player
-> vfxConfigInfo
. warkMarkInfo;
if (g_waterMarkInfo
. bIsUseWaterMark){
if (player
-> vcodec_context
-> width
!= vframe
-> width
|| player
-> vcodec_context
-> height
!= vframe
-> height
|| player
-> vfxConfigInfo
. warkMarkInfo
. bResetWaterMark ){switch (g_waterMarkInfo
. eWaterMarkPos){
case POS_LEFT_TOP:g_waterMarkInfo
. nLeftTopX
= 0 ;g_waterMarkInfo
. nLeftTopY
= 0 ;break;
case POS_RIGHT_TOP:g_waterMarkInfo
. nLeftTopX
= vframe
-> width;g_waterMarkInfo
. nLeftTopY
= 0 ;break;
case POS_LEFT_BOTTOM:g_waterMarkInfo
. nLeftTopX
= 0 ;g_waterMarkInfo
. nLeftTopY
= vframe
-> height;break;
case POS_RIGHT_BOTTOM:g_waterMarkInfo
. nLeftTopX
= vframe
-> width;g_waterMarkInfo
. nLeftTopY
= vframe
-> height;break;}player
-> vfxHandle
-> SetVideoInVideoParam(
101 ,
0 ,
0 , vframe
-> width,vframe
-> height,
100 ,
100 ,
100 );player
-> vfxHandle
-> SetLogoImage(g_waterMarkInfo
. strWMFilePath, g_waterMarkInfo
. nLeftTopX,g_waterMarkInfo
. nLeftTopY, g_waterMarkInfo
. bIsUseWaterMark, g_waterMarkInfo
. eWatermarkStyle);player
-> vfxConfigInfo
. warkMarkInfo
. bResetWaterMark
= FALSE ;}}VideoTittleInfo tittleInfo
= player
-> vfxConfigInfo
. tittleInfo;
if (tittleInfo
. bResetTittleInfo){player
-> vfxHandle
-> CreateOverlayTitle(vframe
-> width, vframe
-> height, (
"YUY2" ));LOGFONTA inFont;inFont
. lfHeight
= tittleInfo
. nTittleHeight;inFont
. lfWidth
= tittleInfo
. nTittleWidth;inFont
. lfEscapement
= 0 ;inFont
. lfOrientation
= 0 ;inFont
. lfWeight
= tittleInfo
. nFontWeight;inFont
. lfItalic
= 0 ;inFont
. lfUnderline
= 0 ;inFont
. lfStrikeOut
= 0 ;inFont
. lfCharSet
= GB2312_CHARSET;inFont
. lfOutPrecision
= 3 ;inFont
. lfClipPrecision
= 2 ;inFont
. lfQuality
= 1 ;inFont
. lfPitchAndFamily
= 0 ;strcpy(inFont
. lfFaceName, tittleInfo
. strFontType);POINT pointTitle;
if (tittleInfo
. nMoveType
== 0 ){pointTitle
= tittleInfo
. ptStartPosition;
if (pointTitle
. x
<= 0 ) pointTitle
. x
= 1 ;
if (pointTitle
. x
>= vframe
-> width) pointTitle
. x
= vframe
-> width/
2 ;}
else if (tittleInfo
. nMoveType
== 1 ){pointTitle
. x
= - 1 ;pointTitle
. y
= tittleInfo
. ptStartPosition
. y;}
else if (tittleInfo
. nMoveType
== 2 ){pointTitle
. x
= vframe
-> width
+ 1 ;pointTitle
. y
= tittleInfo
. ptStartPosition
. y;}player
-> vfxHandle
-> SetOverlayTitleInfo(tittleInfo
. strTittleContent, inFont, tittleInfo
. nColorR, tittleInfo
. nColorG,tittleInfo
. nColorB, pointTitle);player
-> vfxHandle
-> SetOverlayTitleState(tittleInfo
. nState);player
-> vfxConfigInfo
. tittleInfo
. bResetTittleInfo
= FALSE ;}
if (player
-> vfxHandle
&& (g_waterMarkInfo
. bIsUseWaterMark
|| tittleInfo
. nState)){
if (player
-> vcodec_context
-> width
!= vframe
-> width
|| player
-> vcodec_context
-> height
!= vframe
-> height ){
if (pVfxBuffer){free(pVfxBuffer);pVfxBuffer
= NULL ;}}int nBufSize
= vframe
-> width
* vframe
-> height
<< 1 ;
if (
! pVfxBuffer){pVfxBuffer
= (BYTE
* )malloc(nBufSize); memset(pVfxBuffer,
0x00 , nBufSize);}AVFrame src;av_image_fill_arrays(src
. data , src
. linesize, pVfxBuffer, outPixelFormat, vframe
-> width, vframe
-> height,
1 );ConvertColorSpace(
& src, outPixelFormat, vframe, inPixelFormat, vframe
-> width, vframe
-> height);
if (g_waterMarkInfo
. bIsUseWaterMark)player
-> vfxHandle
-> AddWaterMask(pVfxBuffer);
if (tittleInfo
. nState)player
-> vfxHandle
-> DoOverlayTitle(pVfxBuffer);av_image_fill_arrays(vframe
-> data , vframe
-> linesize, pVfxBuffer, outPixelFormat, vframe
-> width, vframe
-> height,
1 );int nPixelFmt
= AV_PIX_FMT_YUYV422;player_setparam(player, PARAM_RENDER_OUTFORMAT,
& nPixelFmt); }
else {int nPixelFmt
= AV_PIX_FMT_YUV420P;player_setparam(player, PARAM_RENDER_OUTFORMAT,
& nPixelFmt); }
#endif
由于視頻渲染需要一定的時(shí)間,我們也將解碼幀數(shù)據(jù)進(jìn)入隊(duì)列進(jìn)行緩存,從而保證播放的流暢性;
(4) 渲染模塊 渲染模塊分為音頻渲染和視頻渲染,音頻渲染即播放,使用waveOutOpen,waveOutWrite等waveout函數(shù)即可實(shí)現(xiàn),下面重點(diǎn)說一下視頻渲染,視頻渲染通俗講也就是圖像繪制,Windows平臺(tái)可采用D3D,DDraw, GDI,OpenGL等多種方式進(jìn)行呈現(xiàn),本文主要采用3種渲染方式,D3D,GDI和OpenGL; 為了保證渲染的流暢性,我們創(chuàng)建線程執(zhí)行渲染, a. 讀取解碼圖像隊(duì)列; b. 音視頻時(shí)間戳同步處理; c. D3D/gdi/openGL渲染:
關(guān)于EasyPlayerPro
EasyPlayerPro是一款全功能的流媒體播放器,支持RTSP、RTMP、HTTP、HLS、UDP、RTP等多種流媒體協(xié)議播放、支持本地文件播放,支持本地抓拍、本地錄像、播放旋轉(zhuǎn)、多屏播放等多種功能特性,穩(wěn)定、高效、可靠,支持Windows、Android、iOS三個(gè)平臺(tái),目前在多家教育、安防、行業(yè)型公司,都得到的應(yīng)用,廣受好評(píng)!
EasyPlayerPro:https://github.com/EasyDSS/EasyPlayerPro
點(diǎn)擊鏈接加入群【EasyPlayer?&?EasyPlayerPro】:544917793
獲取更多信息
郵件:support@easydarwin.org
WEB:www.EasyDarwin.org
Copyright ? EasyDarwin.org 2012-2017
總結(jié)
以上是生活随笔 為你收集整理的EasyPlayerPro(Windows)流媒体播放器开发之框架讲解 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。