原文地址:http://blog.yundiantech.com/?log=blog&id=28

上面我们已经生成了录屏的视频,然而这个视频并不是理想中的那样,随时时间的增加,音视频会越来越不同步。

原因就是因为保存视频的方式采用的是固定帧率的方式,既时间戳间隔也是固定的。

举个栗子:假如视频的帧率是10,就是每秒钟10张图像,那么这十张图像是平均分布的,位置分别是:0.1s、0.2s...0.9s、1s。

然而我们每秒钟采集到的屏幕图像是不固定的,这一秒15张,下一秒有可能只有8张。

当我们用这23张图片用上面的方式去合成视频,产生的视频时长就是2.3秒,而实际明明是2秒。

这样时间一久和音频的差距就出来了。

更重要的是,第一秒获取到的15张图像中的第10~15张会被放到视频的第2秒中去,这样也是有很大的问题。

所以在采集到图像之后需要处理下再保存到视频,处理的方法其实也很简单:

1.第一秒的15张图像 多出了5张 只需要筛选出5张 丢掉即可。

2.第二秒的8张不足2张,只需要找2张图片,重复一下即可。

说白了就是多的丢掉,少了重复一下上一张。

这种方式虽然不是非常完美,但是勉强可以了。

代码实现:

因此我们在采集图像的时候,就需要记录下时间,然后再保存视频的时候,才能根据时间来判断这张图像要不要,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
long time = 0;
if (m_saveVideoFileThread)
{
    if (m_getFirst)
    {
        qint64 secondTime = QDateTime::currentMSecsSinceEpoch();
        time = secondTime - firstTime + timeIndex;//计算相对时间
    }
    else
    {
        firstTime = QDateTime::currentMSecsSinceEpoch();
        timeIndex = m_saveVideoFileThread->getVideoPts()*1000;
        m_getFirst = true;
    }
}
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, packet);
if(ret < 0)
{
    printf("video Decode Error.(解码错误)
");
    return;
}
if(got_frame && pCodecCtx)
{
    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
    if (m_saveVideoFileThread)
    {
        uint8_t * picture_buf = (uint8_t *)av_malloc(size);
        memcpy(picture_buf,pFrameYUV->data[0],y_size);
        memcpy(picture_buf+y_size,pFrameYUV->data[1],y_size/4);
        memcpy(picture_buf+y_size+y_size/4,pFrameYUV->data[2],y_size/4);
        uint8_t * yuv_buf = (uint8_t *)av_malloc(size);
        ///将YUV图像裁剪成目标大小
        Yuv420Cut(pic_x,pic_y,pic_w,pic_h,pCodecCtx->width,pCodecCtx->height,picture_buf,yuv_buf);
        m_saveVideoFileThread->videoDataQuene_Input(yuv_buf,yuvSize*3/2,time);
        av_free(picture_buf);
    }
}

上面采集的线程将图像放到了一个队列中,保存视频的线程只需要从队列中取出图像,并根据时间判断怎么处理即可,代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
BufferDataNode *SaveVideoFileThread::videoDataQuene_get(double time)
{
    BufferDataNode * node = NULL;
    SDL_LockMutex(videoMutex);
    if (videoDataQueneHead != NULL)
    {
        node = videoDataQueneHead;
        if (time >= node->time)
        {
            while(node != NULL)
            {
                if (node->next == NULL)
                {
                    if (isStop)
                    {
                        break;
                    }
                    else
                    {
                        //队列里面才一帧数据 先不处理
                        SDL_UnlockMutex(videoMutex);
                        return NULL;
                    }
                }
                if (time < node->next->time)
                {
                    break;
                }
                BufferDataNode * tmp = node;
                node = node->next;
                videoDataQueneHead = node;
                videoBufferCount--;
                av_free(tmp->buffer);
                free(tmp);
            }
        }
        else
        {
            node = lastVideoNode;
        }
        if (videoDataQueneTail == node)
        {
            videoDataQueneTail = NULL;
        }
        if (node != NULL && node != lastVideoNode)
        {
            videoDataQueneHead = node->next;
            videoBufferCount--;
        }
    }
    SDL_UnlockMutex(videoMutex);
    return node;
}

这样便可解决不同步的问题了。。

既然是录屏软件,那么当然需要录制屏幕局部区域的功能了。

可以再采集的时候设置参数让他直接获取局部区域,然后没找到方法,也不想研究了。

还可以将采集到的YUV420图像直接裁剪出需要的部分,果断用这个方法了,可以学习新技术,又可以装逼,何乐而不为呢。

那就开始执行YUV420P图像的裁剪吧:

首先先来看回顾下YUV420P图像格式:

YUV420p数据格式图

在YUV420中,一个像素点对应一个Y,一个2X2的小方块对应一个U和V。

以下理论是本人自己总结的,没有找到相关文档,不确定准确性,但已经过实测了。

YUV420P的一个U分量是对应4个Y分量的,同样一个V分量也是对应4个Y分量。

所以裁剪的Y分量 必须得是4的倍数,也就是说想要裁掉图中的Y1,那么就需要连带Y2、Y9、Y10也一并裁剪了。这就意味着裁剪掉的部分必须是偶数的大小,不能是奇数,比如想把图像的左边裁掉1个像素是不允许的,只能裁剪2个像素。

现在就以裁掉图像的左边2个像素为例,则对应上图中就是,

去掉Y1 Y2 Y9 Y10 Y17 Y18 Y25 Y26和U1 U5 V1 V5

如下图紫色圈圈所示:

裁剪掉 上、下、左、右都是类似的原理,请自行推理。

理论知识掌握了之后,剩下的就是写代码实现了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void Yuv420Cut(int x,int y,int desW,int desH,int srcW,int srcH,uint8_t *srcBuffer,uint8_t *desBuffer)
{
    int tmpRange;
    int bufferIndex;
    int yIndex = 0;
    bufferIndex = 0 + x + y*srcW;
    tmpRange = srcW * desH;
    for (int i=0;i<tmpRange;) //逐行拷贝Y分量数据
    {
        memcpy(desBuffer+yIndex,srcBuffer+bufferIndex+i,desW);
        i += srcW;
        yIndex += desW;
    }
    int uIndex = desW * desH;
    int uIndexStep = srcW/2;
    int uWidthCopy = desW/2;
    bufferIndex = srcW * srcH+x/2 + y /2 *srcW / 2;
    tmpRange = srcW * desH / 4;
    for (int i=0;i<tmpRange;) //逐行拷贝U分量数据
    {
        memcpy(desBuffer+uIndex,srcBuffer+bufferIndex+i,uWidthCopy);
        i += uIndexStep;
        uIndex += uWidthCopy;
    }
    int vIndex = desW * desH +  desW * desH /4;
    int vIndexStep = srcW/2;
    int vWidthCopy = desW/2;
    bufferIndex = srcW*srcH + srcW*srcH/4 + x/2 + y /2 *srcW / 2;
    tmpRange = srcW * desH / 4;
    for (int i=0;i<tmpRange;) //逐行拷贝V分量数据
    {
        memcpy(desBuffer+vIndex,srcBuffer+bufferIndex+i,vWidthCopy);
        i += vIndexStep;
        vIndex += vWidthCopy;
    }
}

本例子中,我们加入了一个选择屏幕录屏区域的控件。

刚刚说了,裁剪的部分必须是偶数,同时传给ffmpeg编码的图像数据,宽高也必须是偶数。

而我们手动选择录屏区域的时候还是会选到奇数位置的,因此选择区域完毕后需要手动处理一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void MainWindow::slotSelectRectFinished(QRect re)
{
    /// 1.传给ffmpeg编码的图像宽高必须是偶数。
    /// 2.图像裁剪的起始位置和结束位置也必须是偶数
    /// 而手动选择的区域很有可能会是奇数,因此需要处理一下 给他弄成偶数
    /// 处理的方法很简答:其实就是往前或者往后移一个像素
    /// 一个像素的大小肉眼基本也看不出来啥区别。
    int x = re.x();
    int y = re.y();
    int w = re.width();
    int h = re.height();
    if (x % 2 != 0)
    {
        x--;
        w++;
    }
    if (y % 2 != 0)
    {
        y--;
        h++;
    }
    if (w % 2 != 0)
    {
        w++;
    }
    if (h % 2 != 0)
    {
        h++;
    }
    rect = QRect(x,y,w,h);
    QString str = QString("==当前区域==
起点(%1,%2)
大小(%3 x %4)")
            .arg(rect.left()).arg(rect.left()).arg(rect.width()).arg(rect.height());
    ui->showRectInfoLabel->setText(str);
    ui->startButton->setEnabled(true);
    ui->editRectButton->setEnabled(true);
    ui->hideRectButton->setEnabled(true);
    ui->hideRectButton->setText("隐藏");
    saveFile();
}

到这录屏软件已经很完美了。

别的部分就不解释了,自行下载代码查看吧。

完整工程下载地址:http://download.csdn.net/detail/qq214517703/9827493

音视频技术交流讨论欢迎加 QQ群 121376426  


原文地址:http://blog.yundiantech.com/?log=blog&id=28


从零开始学习音视频编程技术(二十一) 录屏软件开发之最终完善相关推荐

  1. 从零开始学习音视频编程技术(二) 音频格式讲解

    从零开始学习音视频编程技术(二) 音频格式讲解 原文地址:http://blog.yundiantech.com/?log=blog&id=5 1. 音频简介 前面我们说过视频有一个每秒钟采集 ...

  2. 从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用

    从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用 原文地址:http://blog.yundiantech.com/?log=blog&id=10 前面介绍了使用F ...

  3. 从零开始学习音视频编程技术--转自雲天之巔

    此为转载文章,主要是为了个人阅读方便,将博主的系列文章罗列出来,点击直接跳转. 从零开始学习音视频编程技术(一) 视频格式讲解 从零开始学习音视频编程技术(二) 音频格式讲解 从零开始学习音视频编程技 ...

  4. 从零开始学习音视频编程技术(六) FFMPEG Qt视频播放器之显示图像

    从零开始学习音视频编程技术(六) FFMPEG Qt视频播放器之显示图像 原文地址:http://blog.yundiantech.com/?log=blog&id=9 前面讲解了如何用FFM ...

  5. 从零开始学习音视频编程技术(四) FFMPEG的使用

    零开始学习音视频编程技术(四) FFMPEG的使用 原文地址:http://blog.yundiantech.com/?log=blog&id=7 音视频开发中最常做的就是编解码的操作了,以H ...

  6. 从零开始学习音视频编程技术(42) AAC数据解析

    AAC基本格式 AAC音频格式有ADIF和ADTS: ADIF:Audio Data Interchange Format 音频数据交换格式.这种格式的特征是可以确定的找到这个音频数据的开始,不需进行 ...

  7. FFMPEG Qt录屏软件开发之视频采集

    前面讲解了视频播放器的开发,初步掌握了使用FFMPEG解码音视频. 现在我们就接着讲解使用FFMPEG来编码音视频,主要是实现一个录屏软件的制作. 一个录屏软件的流程基本就是: 图像采集 图像编码 将 ...

  8. windows录屏html文件,怎样录制电脑屏幕视频 好用的电脑录屏软件,加密视频怎样翻录或婆姐...

    电脑录屏软件是什么意思?简单来说就是录制电脑桌面的操作的软件,除了可以录制电脑桌面操作,屏幕录像软件外还可以录制计算机上播放的视频内容,譬如录制播放器视频.录制QQ视频.录制游戏视频等,录制的结果都是 ...

  9. 科幻照进现实!2020年这个最新编程技术,将完全颠覆软件开发习惯

    这个科幻技术来了,与每个人都有关 2020年,是不少人心中,科幻电影里才会出现的年代,但还有不到三个月的时间为,就要来到这个充满未来科幻感的年代了. 其实,科幻电影里的很多超前的设想,都已经成了我们日 ...

最新文章

  1. num2cell用法
  2. [技术文档] 一劳永逸,用USB设备制作多系统引导
  3. php开发如何测试,用thinkphp开发微信,如何测试?
  4. 基于51单片机的高频频率计的设计
  5. javascript +new Date()
  6. 核聚类与支持向量聚类
  7. STL 里 resize 和 reserve 的区别
  8. [C语言]关于指针和int型的一道题目
  9. mysql设计表月份_mysql,表设计
  10. python-列表演练-根据学生id获取学生数据-获取学生数据中得分较高的前N条数据
  11. 上万规模数据湖如何在实验室测试
  12. ABP理论学习之内嵌资源文件
  13. 如何让Java文件在虚拟机中运行_深入理解JVM--Java程序如何在虚拟机中运行
  14. 字体的样式设置和字体分类
  15. java毕业设计源码介绍 基于SSM美好生活九宫格日志网日记网站
  16. 文本分类概述(nlp)
  17. UserAgent个人整理
  18. 如何进行坡度坡向分析教程
  19. 老挑毛 win7 linux,老挑毛u盘装系统步骤|老挑毛u盘一键装系统
  20. python百万邮件群发软件_python如何群发邮件

热门文章

  1. 【04】穿越功耗墙,我们该从哪些方面提升“性能”?
  2. java根据经纬度获取地址信息(腾讯地图)
  3. 一文读懂 Staking 即服务:机遇与风险,现状与未来
  4. mongodb文档太大_了解MongoDB BSON文档大小限制
  5. 阿里新经济体又添新物种!顶级4A代理群邑鼎力支持天猫双11
  6. 浅谈回声消除中的回声抑制(echo suppress)
  7. 琼斯是计算体心立方弹性模量_大学免费查题的软件,超好用的搜答案神器在这里?...
  8. 【Linux】在Ubuntu中卸载、下载mysql以及如何检查mysql是否卸载成功
  9. 音视频测试中会关注哪些声音问题
  10. 计算出租车费用问题python