视频流媒体RTSP专用播放器RTSP拉流、RTMP推流方案之EasyPlayer-RTSP-Win抓图代码重构流程介绍

指针蝉翼
• 阅读 178

EasyPlayer是一个RTSP专属的视频流媒体播放器,在GitHub上开源大部分源码。其主要功能有播放抓图录制视频实时静音/取消静音

视频流媒体RTSP专用播放器RTSP拉流、RTMP推流方案之EasyPlayer-RTSP-Win抓图代码重构流程介绍

EasyPlayer-RTSP-Win抓图代码重构

EasyPlayer-RTSP-Win(下文简称:EasyPlayer)播放器之前抓图代码主要通过OpenCV来实现,且数据格式转换的效率过于低下;故而在当时的代码中采用线程机制来解决抓图导致视频播放时卡顿的问题;

而最新版的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抓图的步骤分两步:

  1. 需要将图像转换成指定的格式,当然强大的格式转换函数也支持图像的缩放,且效率很高;
  2. 图像编码,细心的同学不难发现,ffmpeg的编码和存文件/推送流的代码是通用的,这套代码可以用来抓图也可以用来编码H264、265等然后存文件(如MP4等)或者推送RTMP/RTSP等;

已经完成了抓图代码调用起来就很简单了,只需替换掉旧的抓图函数即可,需要注意的是之前的抓图固定了格式为YUY2,所以缓冲区大小只有WidthHeight2的大小,而显然RGB24格式的数据会导致缓冲区溢出。

所以,我们需要重新定义缓冲区的大小,如下代码所示:

    //抓图
                    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来定义缓冲区的大小,从而避免不必要的内存资源浪费。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
RTP、RTCP和RTSP协议基础
1RTSP概述1.1RTSP概念RTSP(RealTimeStreamProtocol)是一种基于文本的应用层协议,在语法及一些消息参数等方面,RTSP协议与HTTP协议类似。RTSP被用于建立的控制媒体流的传输,它为多媒体服务扮演“网络远程控制”的角色。RTSP本身并不用于传送媒体流数据。媒体数据的传
Easter79 Easter79
3年前
srs
SRS简介SRS定位是运营级的互联网直播服务器集群,追求更好的概念完整性和最简单实现的代码。SRS提供了丰富的接入方案将RTMP流接入SRS,包括推送RTMP到SRS、推送RTSP/UDP/FLV到SRS、拉取流到SRS。SRS还支持将接入的RTMP流进行各种变换,譬如将RTMP流转码、流截图、转发给其他服务器、转封装成HTTPFLV流、转封
GoCoding GoCoding
4年前
RTSP 流相关工具介绍
RTSP(RealTimeStreamingProtocol),实时流协议,是一种应用层协议,专为流媒体使用。本文将介绍GStreamer,VLC,FFmpeg这几个工具,如何发送、接收RTSP流。前提GStreamerGStreamer:https://gstreamer.freedesktop.org/GStre
GoCoding GoCoding
4年前
FFmpeg 播放 RTSP/Webcam 流
本文将介绍FFmpeg如何播放RTSP/Webcam/File流。流程如下:bashRTSP/Webcam/FileFFmpegopenanddecodetoBGR/YUVOpenCV/OpenGLdisplay代码:https://github.com/ikuokuo/rtspwasmplayer,子模块rtsploca
liuzhen007 liuzhen007
4年前
Golang根据URL判断媒体协议
目录问题解决问题如何根据一个流媒体地址URL判断对应的流媒体协议,比如RTMP、RTSP协议等。解决这里提供一个方法,可以直接拿来用。golangfuncgetProtocol(urlstring)(string,error){ifurl""{index:strings.Index(url,":")
Wesley13 Wesley13
3年前
HTML5播放RTSP,H5播放RTSP,解决方案源码,基于海康网络摄像头
视频是用的海康网络摄像头(支持RTSP,标准H.264RTP封装的设备),可以通过 rtsp://admin:1008@192.0.0.64:81/h264/ch1/main/av\_stream 对摄像头进行读取RSTP流。在谷歌浏览器下实现web显示实时监控画面步骤:!复制代码(https://oscimg.oschina.net/oscne
指针蝉翼
指针蝉翼
Lv1
一去隔绝国,思归但长嗟。
文章
3
粉丝
0
获赞
0