韦东山嵌入式Linux_3期之USB摄像头监控_手机App增加录像功能(二)

本文接上一篇 韦东山嵌入式Linux_3期之USB摄像头监控_手机App增加录像功能(一), 开始介绍对App原框架的修改过程

一、模块划分

   i) (主体模块)视频采集播放

   ii) 显示模式切换

   iii) 拍照

   iv) 录像

   v) fps显示

   vi) 录像的浏览和删除

二、各模块的实现

2.1(主体模块)视频采集播放

2.1.1 参考资料:

1)主体框架(解码、读帧)参考雷霄骅的:100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

      主体框架的流程,可参考雷霄骅的上述博文,这里不再赘述

2)帧的显示,参考:Android 使用 FFmpeg (二)——视屏流播放简单实现

     帧显示的流程,大致如下:

     韦东山嵌入式Linux_3期之USB摄像头监控_手机App增加录像功能(二)

2.2 显示模式切换

实现思路:利用av_filter的scale和pad功能,对获取到的每一个原始帧进行缩放和必要的四边填充。

主框架体代码参考FFMPEG filter使用实例(实现视频缩放,裁剪,水印等),这里不再赘述。

至于怎样实现两种显示模式下的scale参数(以及pad参数)的切换, 尚未找到最优方法(经测,av_opt_set()只对draw_text有效(详见下文:2.5 fps显示),而对scale和pad无效),

目前暂时采用较笨的办法:

1)定义两个filter_descr模板,以及对应的AVFilterGraph、AVFilterContext

/* 用于保持长宽比显示模式 */
const char *m_filter_descr_template = "scale=%d:%d,pad=%d:%d:%d:%d:blue,drawtext=fontfile=/sdcard/data/FreeSerif.ttf:fontsize=20:text=fps:x=(w-tw-%d):y=%d";
char  m_filter_descr[200];
/* 用于全屏显示模式 */
const char *m_filter_descr2_template = "scale=%d:%d,pad=%d:%d:%d:%d:blue,drawtext=fontfile=/sdcard/data/FreeSerif.ttf:fontsize=20:text=fps:x=(w-tw-5):y=5";
char  m_filter_descr2[200];

/* 用于保持长宽比显示模式 */
AVFilterContext *m_buffersink_ctx1;
AVFilterContext *m_buffersrc_ctx1;
AVFilterGraph *m_filter_graph1;

/* 用于全屏显示模式 */
AVFilterContext *m_buffersink_ctx2;
AVFilterContext *m_buffersrc_ctx2;
AVFilterGraph *m_filter_graph2;

2)初始化时,先调用keep_img_AR()来预先计算好两种显示模式对应的filter_descr的值

int keep_img_AR(int nSrcW ,int nSrcH,int nDstW, int nDstH )
{
    /* 计算保持宽高比例后上下有黑边,还是左右有黑边,黑边多少 */
    int imgW = 0,imgH = 0;
    int padW = 0,padH = 0;
    //必须规整为2的倍数,否则ffmpeg计算pad时会报错:Input area not within the padded area or zero-sized
    nDstW = nDstW/2*2;
    nDstH = nDstH/2*2;

    imgW = nSrcW*nDstH/nSrcH/2*2;
    imgH = nSrcH*nDstW/nSrcW/2*2;
    if(imgW<nDstW){
        padW=(nDstW-imgW)/2;
        imgH = nDstH/2*2;
        //imgW = -1;
    }
    else if(imgH<nDstH){
        padH=(nDstH-imgH)/2;
        imgW = nDstW/2*2;
        //imgH = -1;
    }
    sprintf(m_filter_descr, m_filter_descr_template, imgW, imgH, nDstW, nDstH, padW, padH, padW+5, padH+5);
    sprintf(m_filter_descr2, m_filter_descr2_template, nDstW, nDstH, nDstW, nDstH, 0, 0);

    return 1 ;
}

3)然后调用init_filters()来初始化m_filter_graph1、m_buffersink_ctx1、m_buffersrc_ctx1和m_filter_graph2、m_buffersink_ctx2、m_buffersrc_ctx2

     init_filters()的代码参考FFMPEG filter使用实例(实现视频缩放,裁剪,水印等),这里不再赘述

4)而切换播放模式,其实就是切换(m_filter_graph1、m_buffersink_ctx1、m_buffersrc_ctx1)和(m_filter_graph2、m_buffersink_ctx2、m_buffersrc_ctx2)三元组

/**
 * 播放视频时保持长宽比
 */
void playVideoKeepAspectRatio()
{
    m_play_video_mode = PLAY_VIDEO_KEEP_ASPECT_RATIO;
    m_filter_graph =   m_filter_graph1;
    m_buffersrc_ctx = m_buffersrc_ctx1;
    m_buffersink_ctx = m_buffersink_ctx1;
}

/**
 * 播放视频时填满显示区域
 */
void playVideoFullScreen( )
{
    m_play_video_mode = PLAY_VIDEO_FULL_SCREEN;
    m_filter_graph =   m_filter_graph2;
    m_buffersrc_ctx = m_buffersrc_ctx2;
    m_buffersink_ctx = m_buffersink_ctx2;
}

注:关于显示模式切换,另一种实现的办法是利用sws_scale()和av_picture_pad(),参考:使用ffmpeg的lib库实现视频窗口 原始宽高比例/拉伸铺满

但代码量较大,而且经测试,发现有些问题,比如:

- 加上av_filter的draw_text后,fps的显示会出现小幅度的上下跳动,原因待查

- fps的定位较难实现(因为要考虑到pad的宽度)

所以最终没有采用这个办法(但keep_img_AR()里计算scale和pad的算法参考了这篇文章)。

2.3 拍照

实现思路:

1)定义m_pFrameCur代表当前获取到的帧

2)在视频播放函数videoStreamStartPlay()的while循环里,用av_frame_ref(m_pFrameCur, pFrame)使m_pFrameCur指向当前获取到的帧

3)__save_frame_2_jpeg(file_path, m_pFrameCur, m_input_codec_ctx->pix_fmt)实现把当前帧保存到指定的文件中

     代码参考:ffmpeg实现mjpeg摄像头的采集-预览-拍照,这里不再赘述

2.4 录像

参考:如何用FFmpeg API采集摄像头视频和麦克风音频,并实现录制文件的功能

该文章的demo里,把录像功能很好的封装在了一个类CAVOutputStream里,我基本上原封不动的拿来用于录像功能的底层实现。

我所添加的工作,是在视频播放函数videoStreamStartPlay()的while循环里,调用状态机video_capture_state_machine(),代码大致如下:

void video_capture_state_machine(AVFrame *pFrame)
{
    switch(m_video_capture_state)
    {
        case VIDEO_CAPTURE_START:
            LOGD("VIDEO_CAPTURE_START");
            m_start_time = av_gettime();
            m_OutputStream.SetVideoCodec(AV_CODEC_ID_H264); //设置视频编码器属性
            if(true == m_OutputStream.OpenOutputStream(m_save_video_path.c_str()))
                m_video_capture_state = VIDEO_CAPTURE_IN_PROGRESS;
            else
                m_video_capture_state = VIDEO_CAPTURE_IDLE;
            break;
        case VIDEO_CAPTURE_IN_PROGRESS:
            LOGD("VIDEO_CAPTURE_IN_PROGRESS");
            m_OutputStream.write_video_frame(m_input_format_ctx->streams[m_video_stream_index], m_input_format_ctx->streams[m_video_stream_index]->codec->pix_fmt, pFrame, av_gettime() - m_start_time);
            break;
        case VIDEO_CAPTURE_STOP:
            LOGD("VIDEO_CAPTURE_STOP");
            m_OutputStream.CloseOutput();
            m_video_capture_state = VIDEO_CAPTURE_IDLE;
            break;
        default:
            if(m_video_capture_state == VIDEO_CAPTURE_IDLE){
                LOGD("VIDEO_CAPTURE_IDLE");
            }
            else{
                LOGD("m_video_capture_state: %d", m_video_capture_state);
            }
            break;
    }//eo switch(m_video_capture_state)
}

而native层和JAVA层的接口如下:

/* 开始录像 */
void videoStreamStartCapture(const char* file_path)
{
    m_save_video_path = file_path;
    m_video_capture_state = VIDEO_CAPTURE_START;
}

/* 停止录像 */
void videoStreamStopCapture( )
{
    m_video_capture_state = VIDEO_CAPTURE_STOP;
}

2.5 fps显示

实现思路同:2.2 显示模式切换

而fps值的动态显示,是利用av_opt_set(filter_ctx_draw_text->priv, "text", str_fps, 0 )来实现的。

2.6 录像的浏览和删除

实现思路:基本上利用了app的原框架,只做了少量改动。主要如下:

1) MainActivity.java

      当用户点击“照片”按钮后,弹出AlertDialog,提示选择浏览类型,然后根据用户的选择,在startActivity(intent)前,调用

      intent.putExtra("picturePath", picturePath);

      intent.putExtra("scan_type", ScanPicActivity.SCAN_TYPE_VIDEO);

      或者

      intent.putExtra("picturePath", videoRecordPath);

      intent.putExtra("scan_type", ScanPicActivity.SCAN_TYPE_PIC);

2)ScanPicActivity.java

      - 在init()函数中,scan_type =  getIntent().getIntExtra("scan_type", SCAN_TYPE_PIC);保存当前的浏览类型

      - 在每一处涉及“jpeg”字符串的地方,都加入scan_type判断。代码从略,详见项目源代码

3)Generic.java

      仿照getShrinkedPic(),添加函数getShrinkedPicFromVideo(),核心是ThumbnailUtils.createVideoThumbnail()。代码从略,详见项目源代码

参考资料:

1)韦东山嵌入式linux培训3期项目实战之usb摄像头监控,手机App源代码

2)Android 官方教程:https://developer.android.google.cn/guide/

3)AndroidStudio3.x开发调试Android-NDK的C++代码

4)NDK开发笔记---CMake构建JNI

5)雷霄骅的博客系列文章:[总结]FFMPEG视音频编解码零基础学习方法

6)Android 使用 FFmpeg (二)——视屏流播放简单实现

7)如何用FFmpeg API采集摄像头视频和麦克风音频,并实现录制文件的功能

8)ffmpeg实现mjpeg摄像头的采集-预览-拍照

9)FFMPEG filter使用实例(实现视频缩放,裁剪,水印等)

10)使用ffmpeg的lib库实现视频窗口 原始宽高比例/拉伸铺满

11)ffmpeg实现动态调整字幕内容

12)Ffmpeg用法总结(下)

13)在Android logcat中打印FFmpeg调试信息

韦东山嵌入式Linux_3期之USB摄像头监控_手机App增加录像功能(二)

上一篇:逻辑运算符、位运算符以及三元运算符及小结


下一篇:Oracle创建数据库