EasyPlayer是一個RTSP專屬的視頻流媒體播放器,在GitHub上開源大部分源碼。其主要功能有播放、抓圖、錄製視頻、實時靜音/取消靜音。架構
EasyPlayer-RTSP-Win(下文簡稱:EasyPlayer)播放器以前抓圖代碼主要經過OpenCV來實現,且數據格式轉換的效率過於低下;故而在當時的代碼中採用線程機制來解決抓圖致使視頻播放時卡頓的問題;ide
而最新版的EasyPlayer爲了精簡代碼也爲了提升抓圖效率,採用ffmpeg進行抓圖,爲了保證視頻播放的流暢性,線程機制仍然保留。函數
採用ffmpeg進行抓圖代碼以下:大數據
// 抓圖函數實現 int take_snapshot(char *file, int w, int h, uint8_t *buffer, AVPixelFormat Format) { char *fileext = NULL; enum AVCodecID codecid = AV_CODEC_ID_NONE; struct SwsContext *sws_ctx = NULL; AVPixelFormat swsofmt = AV_PIX_FMT_NONE; AVFrame picture = {}; int ret = -1; AVFormatContext *fmt_ctxt = NULL; AVOutputFormat *out_fmt = NULL; AVStream *stream = NULL; AVCodecContext *codec_ctxt = NULL; AVCodec *codec = NULL; AVPacket packet = {}; int retry = 8; int got = 0; // init ffmpeg av_register_all(); fileext = file + strlen(file) - 3; if (_stricmp(fileext, "png") == 0) { codecid = AV_CODEC_ID_APNG; swsofmt = AV_PIX_FMT_RGB24; } else { codecid = AV_CODEC_ID_MJPEG; swsofmt = AV_PIX_FMT_YUVJ420P; } AVFrame video; int numBytesIn; numBytesIn = av_image_get_buffer_size(Format, w, h, 1); av_image_fill_arrays(video.data, video.linesize, buffer, Format, w, h, 1); video.width = w; video.height = h; video.format = Format; // alloc picture picture.format = swsofmt; picture.width = w > 0 ? w : video.width; picture.height = h > 0 ? h : video.height; int numBytes = av_image_get_buffer_size(swsofmt, picture.width, picture.height , 1); buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(picture.data, picture.linesize, buffer, swsofmt, picture.width, picture.height, 1); // scale picture sws_ctx = sws_getContext(video.width, video.height, (AVPixelFormat)Format/*video->format*/, picture.width, picture.height, swsofmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { //av_log(NULL, AV_LOG_ERROR, "could not initialize the conversion context jpg\n"); goto done; } sws_scale(sws_ctx, video.data, video.linesize, 0, video.height, picture.data, picture.linesize); // do encoding fmt_ctxt = avformat_alloc_context(); out_fmt = av_guess_format(codecid == AV_CODEC_ID_APNG ? "apng" : "mjpeg", NULL, NULL); fmt_ctxt->oformat = out_fmt; if (!out_fmt) { //av_log(NULL, AV_LOG_ERROR, "failed to guess format !\n"); goto done; } if (avio_open(&fmt_ctxt->pb, file, AVIO_FLAG_READ_WRITE) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to open output file: %s !\n", file); goto done; } stream = avformat_new_stream(fmt_ctxt, 0); if (!stream) { //av_log(NULL, AV_LOG_ERROR, "failed to create a new stream !\n"); goto done; } codec_ctxt = stream->codec; codec_ctxt->codec_id = out_fmt->video_codec; codec_ctxt->codec_type = AVMEDIA_TYPE_VIDEO; codec_ctxt->pix_fmt = swsofmt; codec_ctxt->width = picture.width; codec_ctxt->height = picture.height; codec_ctxt->time_base.num = 1; codec_ctxt->time_base.den = 25; codec = avcodec_find_encoder(codec_ctxt->codec_id); if (!codec) { //av_log(NULL, AV_LOG_ERROR, "failed to find encoder !\n"); goto done; } if (avcodec_open2(codec_ctxt, codec, NULL) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to open encoder !\n"); goto done; } while (retry-- && !got) { if (avcodec_encode_video2(codec_ctxt, &packet, &picture, &got) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to do picture encoding !\n"); goto done; } if (got) { ret = avformat_write_header(fmt_ctxt, NULL); if (ret < 0) { //av_log(NULL, AV_LOG_ERROR, "error occurred when opening output file !\n"); goto done; } av_write_frame(fmt_ctxt, &packet); av_write_trailer(fmt_ctxt); } } // ok ret = 0; done: avcodec_close(codec_ctxt); if (fmt_ctxt) { avio_close(fmt_ctxt->pb); } avformat_free_context(fmt_ctxt); av_packet_unref(&packet); sws_freeContext(sws_ctx); av_free(buffer); return ret; }
藉助ffmpeg強大的視頻處理和轉換功能,咱們能夠將一幀圖像轉換成任意格式的圖片,固然如代碼所示咱們只選擇性地支持了「jpeg」和「png」兩種格式的圖片格式;優化
採用ffmpeg抓圖的步驟分兩步:ui
已經完成了抓圖代碼調用起來就很簡單了,只需替換掉舊的抓圖函數便可,須要注意的是以前的抓圖固定了格式爲YUY2,因此緩衝區大小隻有WidthHeight2的大小,而顯然RGB24格式的數據會致使緩衝區溢出。編碼
因此,咱們須要從新定義緩衝區的大小,以下代碼所示:spa
//抓圖 if (pThread->manuScreenshot == 0x01 )//Just support jpeg,png { unsigned int timestamp = (unsigned int)time(NULL); time_t tt = timestamp; struct tm *_time = localtime(&tt); char szTime[64] = {0,}; strftime(szTime, 32, "%Y%m%d-%H%M%S", _time); // char strPath[512] = {0,}; // sprintf(strPath , "%sch%d_%s.jpg", pThread->strScreenCapturePath, pThread->channelId, szTime) ; PhotoShotThreadInfo* pShotThreadInfo = new PhotoShotThreadInfo; sprintf(pShotThreadInfo->strPath , "%sch%d_%s.jpg", pThread->strScreenCapturePath, pThread->channelId, szTime) ; int nYuvBufLen = frameinfo.width*frameinfo.height*3;// most size = RGB24, we donot support RGBA Render type pShotThreadInfo->pYuvBuf = new unsigned char[nYuvBufLen]; pShotThreadInfo->width = frameinfo.width; pShotThreadInfo->height = frameinfo.height; pShotThreadInfo->renderFormat = pThread->renderFormat ; memcpy(pShotThreadInfo->pYuvBuf, pThread->yuvFrame[pThread->decodeYuvIdx].pYuvBuf, pThread->yuvFrame[pThread->decodeYuvIdx].Yuvsize-1); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_lpPhotoShotThread, pShotThreadInfo, 0, NULL); pThread->manuScreenshot = 0; }
目前所支持的最大數據格式是RGB24,因此定義了WidthHeight3+1的最大緩衝區大小,其實這裏能夠優化一下,就是根據具體的renderFormat來定義緩衝區的大小,從而避免沒必要要的內存資源浪費。線程