日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

EasyPlayerPro(Windows)流媒体播放器开发之框架讲解

發布時間:2024/3/24 windows 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EasyPlayerPro(Windows)流媒体播放器开发之框架讲解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

EasyPlayerPro for Windows是基于ffmpeg進行開發的全功能播放器,開發過程中參考了很多開源的播放器,諸如vlc和ffplay等,其中最強大的莫過于vlc,但是鑒于vlc框架過于龐大而其中仍存在諸多問題而舍棄了,而其他的更傾向于演示demo,只能提供部分借鑒意義;故而,EasyPlayerPro 一貫秉承Easy系列小而精,接口簡單功能強大的宗旨從新設計了一套框架,該套框架能適應多線程調用以及多個播放實例同時運行,和EasyPlayer一樣Easy; 當然,在此也鄭重的感謝各大開源播放器以及ffmpeg的作者的無私奉獻。

EasyPlayerPro分為三大模塊:打開模塊,讀取流數據模塊,解碼模塊和渲染模塊,其中:

(1) 打開模塊
打開流模塊很簡單,教科書式的調用方法:

player->avformat_context = avformat_alloc_context();player->avformat_context->interrupt_callback.callback = interrupt_cb;player->avformat_context->interrupt_callback.opaque = player;// open input fileAVDictionary *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 infoif (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 contextif (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 codecif (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 contextif (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 codecif (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 audioif (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 issueif (alayout == 0) {alayout = av_get_default_channel_layout(player->acodec_context->channels);}//-- fix audio channel layout issue}// for videoif (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打開一個流,為了避免在打開流的時候出現阻塞,我們創建一個線程來執行,同時,為了防止ffmpeg內部出現持久行的阻塞,我們傳入阻塞回調函數,在關閉流或者其他必要的時候解除阻塞;avformat_find_stream_info獲取流的解碼信息,根據音視頻以及字幕的解碼信息初始化解碼器;

(2) 讀取流數據模塊

retv = av_read_frame(player->avformat_context, packet);//++ play completed ++//if (retv < 0){if (player->avformat_context->pb && player->avformat_context->pb->error){ //告知播放實時流中斷player->error_flag = 1;//創建斷線重連錯誤檢測線程// [9/4/2017 swordtwelve]break;}player->player_status |= PS_D_PAUSE;pktqueue_write_post_i(player->pktqueue, packet);usleep(20*1000); continue;}//-- play completed --//player->error_flag = 0;//-1=初始化 0=正常 1-n錯誤代碼// audioif (packet->stream_index == player->astream_index){pktqueue_write_post_a(player->pktqueue, packet);}// videoif (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); // free packetpktqueue_write_post_i(player->pktqueue, packet);}}

讀取數據模塊超級簡單,創建一個線程循環執行av_read_frame,讀取到一幀就將其放入隊列,這里采用了ffplay的阻塞的方式來處理隊列的消費者和生產者的問題,這塊有待優化,后續將改成無鎖循環隊列模式,如EasyPlayer。

(3) 解碼模塊
解碼模塊分為音頻和視頻解碼模塊,音視頻的解碼流程非常相似,
主要分為三步:
a. 從隊列中讀取音視頻編碼數據;
b. 音視頻分別采用avcodec_decode_audio4和avcodec_decode_video2進行解碼;
c. 音視頻渲染;
這里著重講解視頻的解碼后的過程,其中涉及到解碼后的原始圖像數據進行處理,解碼出一幀圖像以后,我們需要對其進行字幕和圖像或者其他的視頻圖像的疊加,借助ffmpeg強大的圖像轉換和縮放能力,借助VFX庫我們很容易實現:

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) {// 解碼視頻幀添加特技處理 [9/7/2017 dingshuai]// 1. 疊加圖片// 2. 疊加字母// 3. 畫框...// 對解碼幀進行特技處理(字符,圖片疊加,添加特效) [Dingshuai 2017/08/07] #if 1WaterMarkInfo 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 ){//初始化水印疊加//;表示臺標位置:1 == 左上 2 == 右上 3 == 左下 4 == 右下//eWaterMarkPos = 3//;水印頂點x軸坐標,建議不小于0;不大于視頻寬度//nLeftTopX = 0//;水印頂點y軸坐標,建議不小于0;不大于視頻高度//nLeftTopY = 480//;水印風格:0 - 6//eWatermarkStyle = 3//;水印圖像文件路徑LOGO.png//strWMFilePath = .\Res\logo.pngswitch (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){// -->1、初始化創建字幕指針,并初始化視頻長寬參數 m_pVideoVfxMakerInfo->nDesWidth, m_pVideoVfxMakerInfo->nDesHeight, m_pVideoVfxMakerInfo->strDesBytesType);player->vfxHandle->CreateOverlayTitle(vframe->width, vframe->height, ("YUY2"));// -->2、設置字幕文字信息LOGFONTA inFont;inFont.lfHeight = tittleInfo.nTittleHeight;inFont.lfWidth = tittleInfo.nTittleWidth;inFont.lfEscapement = 0;inFont.lfOrientation = 0;inFont.lfWeight = tittleInfo.nFontWeight;//FW_NORMAL;inFont.lfItalic = 0;inFont.lfUnderline = 0;inFont.lfStrikeOut = 0;inFont.lfCharSet =GB2312_CHARSET;// ANSI_CHARSET;//134inFont.lfOutPrecision =3;// OUT_DEFAULT_PRECIS;inFont.lfClipPrecision = 2;//CLIP_DEFAULT_PRECIS;inFont.lfQuality = 1;//PROOF_QUALITY;inFont.lfPitchAndFamily = 0;//49;//49strcpy(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);//-->3、設置字幕運行抓狀態player->vfxHandle->SetOverlayTitleState(tittleInfo.nState);player->vfxConfigInfo.tittleInfo.bResetTittleInfo = FALSE;}if (player->vfxHandle && (g_waterMarkInfo.bIsUseWaterMark || tittleInfo.nState))//logo-水印 + 字幕 + ???{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);//YUV420 -> YUY2ConvertColorSpace(&src, outPixelFormat, vframe, inPixelFormat, vframe->width, vframe->height); // av_image_copy_to_buffer(pVfxBuffer, nBufSize, // vframe->data, vframe->linesize, AV_PIX_FMT_YUYV422, vframe->width, vframe->height, 1);//水印疊加if(g_waterMarkInfo.bIsUseWaterMark)player->vfxHandle->AddWaterMask(pVfxBuffer);//OSD疊加if(tittleInfo.nState)player->vfxHandle->DoOverlayTitle(pVfxBuffer);//YUY2 -> I420//ConvertColorSpace(vframe, inPixelFormat, &src, outPixelFormat, vframe->width, vframe->height);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

由于視頻渲染需要一定的時間,我們也將解碼幀數據進入隊列進行緩存,從而保證播放的流暢性;

(4) 渲染模塊
渲染模塊分為音頻渲染和視頻渲染,音頻渲染即播放,使用waveOutOpen,waveOutWrite等waveout函數即可實現,下面重點說一下視頻渲染,視頻渲染通俗講也就是圖像繪制,Windows平臺可采用D3D,DDraw, GDI,OpenGL等多種方式進行呈現,本文主要采用3種渲染方式,D3D,GDI和OpenGL;
為了保證渲染的流暢性,我們創建線程執行渲染,
a. 讀取解碼圖像隊列;
b. 音視頻時間戳同步處理;
c. D3D/gdi/openGL渲染:

關于EasyPlayerPro

EasyPlayerPro是一款全功能的流媒體播放器,支持RTSP、RTMP、HTTP、HLS、UDP、RTP等多種流媒體協議播放、支持本地文件播放,支持本地抓拍、本地錄像、播放旋轉、多屏播放等多種功能特性,穩定、高效、可靠,支持Windows、Android、iOS三個平臺,目前在多家教育、安防、行業型公司,都得到的應用,廣受好評!

EasyPlayerPro:https://github.com/EasyDSS/EasyPlayerPro

點擊鏈接加入群【EasyPlayer?&?EasyPlayerPro】:544917793

獲取更多信息

郵件:support@easydarwin.org

WEB:www.EasyDarwin.org

Copyright ? EasyDarwin.org 2012-2017

總結

以上是生活随笔為你收集整理的EasyPlayerPro(Windows)流媒体播放器开发之框架讲解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。