視頻流媒體RTSP專用播放器RTSP拉流、RTMP推流方案之EasyPlayer-RTSP-Win抓圖代碼重構流程介紹

EasyPlayer是一個RTSP專屬的視頻流媒體播放器,在GitHub上開源大部分源碼。其主要功能有播放抓圖錄製視頻實時靜音/取消靜音架構

EasyPlayer架構圖.png

EasyPlayer-RTSP-Win抓圖代碼重構

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

  1. 須要將圖像轉換成指定的格式,固然強大的格式轉換函數也支持圖像的縮放,且效率很高;
  2. 圖像編碼,細心的同窗不難發現,ffmpeg的編碼和存文件/推送流的代碼是通用的,這套代碼能夠用來抓圖也能夠用來編碼H26四、265等而後存文件(如MP4等)或者推送RTMP/RTSP等;

已經完成了抓圖代碼調用起來就很簡單了,只需替換掉舊的抓圖函數便可,須要注意的是以前的抓圖固定了格式爲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來定義緩衝區的大小,從而避免沒必要要的內存資源浪費。線程

相關文章
相關標籤/搜索