一、JPEG和MJPG编码介绍

1、JPEG编码

我个人简单的理解是,JPEG即是Joint Photographic Experts Group(联合图像专家组)的缩写,更是一种图像压缩编码算法。JPEG编码算法过程简单可以归结于下:其中DCT变换和量化是有损的,而熵编码(一般是哈夫曼编码)是无损的。量化和编码都可以通过量化表和编码表查询得到。

2、MJPG编码

Motion JPEG是一种基于静态图像JPEG 压缩标准的动态图像压缩标准,压缩时将连续图像的每一个帧视为一幅静止图像进行压缩,从而可以生成序列化运动图像。压缩时不对帧间的时间冗余进行压缩,也就是说没有MJPEG或者H264那样的帧间编码,阵内预测编码也没有,所以较于实现,只是压缩效率较低。一个MJPG帧序列可以看出是N帧JPEG编码后的数据流连接而成。

二、基于MJPG编码的AVI视频封装介绍

AVI是一种RIFF(Resource Interchange File Format)文件格式,多用于音视频捕捉、编辑、回放等应用程序。AVI包含三个部分:文件头、数据块和索引块。其中文件头包括文件的通用信息,定义数据格式,所用的压缩算法等参数。数据块包含实际数据流,即图像和声音序列数据。这是文件的主体,也是决定文件容量的主要部分。视频文件的大小等于该文件的数据率乘以该视频播放的时间长度。索引块包括数据块列表和它们在文件中的位置,以提供文件内数据随机存取能力。AVI文件的总体结构:

三、ZED上JPEG编码实现

整个编码过程比较繁琐,这里只做简单介绍。后续如果有时间,专门开辟一篇博客介绍JPEG编码过程。

1、主编码

 void mjpg::jpeg_encode(unsigned char **yuv_buffer_pointer)
{
unsigned int remnant;
yuv_p = *yuv_buffer_pointer; bitstring fillbits; //filling bitstring for the bit alignment of the EOI marker //fp_jpeg_stream=fopen("000.jpg","wb");
jpgsize = ;
// 505 bytes
writeword(0xFFD8); // SOI 2
write_APP0info();//JIFF
// write_comment("Cris made this JPEG with his own encoder");
write_DQTinfo();//= 0xFFDB
write_SOF0info();//FFC0
write_DHTinfo();
write_SOSinfo(); //jpgsize = 505; // init global variables
bytenew = ; // current byte
bytepos = ; // bit position in this byte
main_encoder(); // Do the bit alignment of the EOI marker
if (bytepos >= )
{
fillbits.length = bytepos + ;
fillbits.value = (<<(bytepos+)) - ;
writebits(fillbits);
}
writeword(0xFFD9); // EOI //remnant = (~(jpgsize&0x00000003))&0x00000003;// important
remnant = (-(jpgsize&0x00000003))&0x00000003;// important
jpgsize = jpgsize + remnant;
movisize = movisize + jpgsize;
while(remnant > )
{
fputc(,avi_jpeg_stream);
remnant--;
}
}

其中remnant是一个计算一帧图像大小的余数,因为后续AVI封装要求每帧图像大小都是4的整数倍。

2、获取8x8阵列数据

每次处理都是以8x8大小的数据矩阵进行处理的。由于USB摄像头采集到的图像数据是YUV422打包格式,而JPEG编码中比较多的是使用YUV411,所以优先考虑将其转换。其中yuv_p是原始YUV422图像数据指针,YDU1~YDU4是四个连续存储Y分量大小为64字节的数组,CbDU和CrDU分别为Cb和Cr分量。为了提高计算效率,乘法均由移位完成

 void mjpg::load_data_units_from_YUV_buffer(WORD xpos, WORD ypos)
{
BYTE x, y;
BYTE pos = ;
DWORD location;
// SBYTE Cr_temp,Cb_temp; //location = ypos * 640+ xpos;
location = (ypos<<) + (ypos<<) + xpos;
for (y=; y<; y++)
{
for (x=; x<; x++)
{
YDU1[pos] = *(yuv_p+((location)<<)) -;
YDU2[pos] = *(yuv_p+((location+)<<)) -;
YDU3[pos] = *(yuv_p+((location+)<<)) -;//location = (ypos+8) * 640+ xpos+8;
YDU4[pos] = *(yuv_p+((location+)<<)) -;//location = (ypos+8) * 640+ xpos+8;
pos++;
location++;
}
location += ;//640 - 8;
} pos = ;
//location = ypos * 640+ xpos;
location = (ypos<<) + (ypos<<) + xpos;
for (y=; y<; y++)
{
for (x=; x<; x++)
{
CbDU[pos] = *(yuv_p+(location)*+)-;
CrDU[pos] = *(yuv_p+(location+)*+)-;
pos++;
location++;
location++;
}
location += ;//640*2 - 16;
}
}

3、对每个8x8数据阵列进行JPEG处理

void mjpg::main_encoder()
{
SWORD DCY = , DCCb = , DCCr = ; //DC coefficients used for differential encoding
WORD xpos, ypos; for (ypos=; ypos<IMG_HEIGTH; ypos+=)
{
for (xpos=; xpos<(IMG_WIDTH); xpos+=)
{
load_data_units_from_YUV_buffer(xpos, ypos);
process_DU(YDU1, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
process_DU(YDU2, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
process_DU(YDU3, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
process_DU(YDU4, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
process_DU(CbDU, fdtbl_Cb, &DCCb, CbDC_HT, CbAC_HT);
process_DU(CrDU, fdtbl_Cb, &DCCr, CbDC_HT, CbAC_HT);
}
}
}

四、ZED上MJPG的编码实现以及AVI封装

写avi文件第一步是写hdrl头信息,可是hdrl头信息需要确定文件的总帧数和文件大小,而在采集过程中这些都是不确定的(因为不知道什么时候采集结束),为此采用了一个“偷懒”方法:先写一个虚假的hdrl,然后每次对一帧图像进行JPEG编码后,将图像的数据量mjpgfile->movisize记录下来,并将数据帧数framecnt记录下来。停止采集后先结束avi文件的写入,再重新打开,然后对文件头进行修改;或者通过fseek寻找的头文件位置,同样修改hdrl信息。两种方法我都试过,感觉效率都差不多。

为了方便采集,添加按键来触发改变需要的状态,定义state为3个状态:

state--含义
0------idle,等待采集
1------正在采集
2------结束采集

state为0时,标明需要准备写一个新的avi文件;state为1时,标明现在正在采集图像数据,并对每一帧进行jpeg编码;state为2时,标明采集已经结束,fseek往回修改头文件。新的paintEvent函数:

 void Widget::paintEvent(QPaintEvent *)
{
rs = vd->get_frame(&yuv_buffer_pointer,&len); if(last_state== && state == )
{
//write hdrl
hdrl.avih.width =;// (width);
hdrl.avih.height = ;//(height);
hdrl.strl.strf.width = ;//(width);
hdrl.strl.strf.height = ;//(height);
hdrl.strl.strf.image_sz = **;//(width * height * 3); sizeofhdrl=sizeof(hdrl); mjpgfile->avi_jpeg_stream = fopen(avifilename, "wb"); fputc('R', mjpgfile->avi_jpeg_stream);
fputc('I', mjpgfile->avi_jpeg_stream);
fputc('F', mjpgfile->avi_jpeg_stream);
fputc('F', mjpgfile->avi_jpeg_stream);
print_quartet(/*riff_sz*/);//riff file size
fputc('A', mjpgfile->avi_jpeg_stream);
fputc('V', mjpgfile->avi_jpeg_stream);
fputc('I', mjpgfile->avi_jpeg_stream);
fputc(' ', mjpgfile->avi_jpeg_stream); fwrite(&hdrl, sizeofhdrl, , mjpgfile->avi_jpeg_stream);// write head fputc('L', mjpgfile->avi_jpeg_stream);
fputc('I', mjpgfile->avi_jpeg_stream);
fputc('S', mjpgfile->avi_jpeg_stream);
fputc('T', mjpgfile->avi_jpeg_stream); print_quartet(/*jpg_sz + 8*TOTALFRAMES + 4*/);// size again
fputc('m', mjpgfile->avi_jpeg_stream);
fputc('o', mjpgfile->avi_jpeg_stream);
fputc('v', mjpgfile->avi_jpeg_stream);
fputc('i', mjpgfile->avi_jpeg_stream); avifilename[]++;
} if(state==)
{
framecnt++;
fputc('', mjpgfile->avi_jpeg_stream);
fputc('', mjpgfile->avi_jpeg_stream);
fputc('d', mjpgfile->avi_jpeg_stream);
fputc('c', mjpgfile->avi_jpeg_stream);
print_quartet(); mjpgfile->jpeg_encode(&yuv_buffer_pointer); //printf("%ld\n",mjpgfile->jpgsize); fseek(mjpgfile->avi_jpeg_stream,--(long)mjpgfile->jpgsize,SEEK_CUR);
print_quartet(mjpgfile->jpgsize); fseek(mjpgfile->avi_jpeg_stream,,SEEK_CUR);
fwrite("AVI1",, , mjpgfile->avi_jpeg_stream); fseek(mjpgfile->avi_jpeg_stream,mjpgfile->jpgsize-,SEEK_CUR); }
if(last_state== && state==)
{ fseek(mjpgfile->avi_jpeg_stream,,SEEK_SET);
print_quartet(mjpgfile->movisize+sizeofhdrl);//riff file size
fseek(mjpgfile->avi_jpeg_stream,,SEEK_CUR); //overwrite hdrl
hdrl.avih.us_per_frame = /;//(per_usec);
hdrl.avih.max_bytes_per_sec = mjpgfile->movisize*/framecnt;
hdrl.avih.tot_frames = framecnt;
hdrl.strl.list_odml.frames =framecnt;// (TOTALFRAMES);
hdrl.strl.strh.scale = ;//
hdrl.strl.strh.length =;//
hdrl.strl.strh.rate = ; fwrite(&hdrl, sizeofhdrl, , mjpgfile->avi_jpeg_stream);// write head
fseek(mjpgfile->avi_jpeg_stream,,SEEK_CUR); print_quartet(mjpgfile->movisize);// size again
fclose(mjpgfile->avi_jpeg_stream);
}
last_state=state; convert_yuv_to_rgb_buffer(); frame->loadFromData(rgb_buffer, * * );
ui->label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor)); rs = vd->unget_frame();
}

最开始定义了视频名字的数组,char avifilename[11] = {'r','c','q','0','0','0','.','a','v','i','\0'};

在42行:avifilename[5]++;

表示让名字由"rcq000.avi"依次计数增加。

五、测试效果

可执行程序:

17、MJPG编码和AVI封装的更多相关文章

  1. MediaCodec编码结合FFmpeg封装流

    在Android平台上合成视频一般使用MediaCodec进行硬编码,使用MediaMuxer进行封装,但是因为MediaMuxer在某些机型上合成的视频在其他手机上播放会出现问题,而且只支持一个音频 ...

  2. Vue 2.x折腾记 - (17) 基于Ant Design Vue 封装一个配置式的表单组件

    前言 写了个类似上篇搜索的封装,但是要考虑的东西更多. 具体业务比展示的代码要复杂,篇幅太长就不引入了. 效果图 2019-04-25 添加了下拉多选的渲染,并搜索默认过滤文本而非值 简化了渲染的子组 ...

  3. 基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集【转】

    转自:http://www.cnblogs.com/surpassal/archive/2012/12/19/zed_webcam_lab1.html 一直想把USB摄像头接到Zedboard上,搭建 ...

  4. 集显也能硬件编码:Intel SDK && 各种音视频编解码学习详解

    http://blog.sina.com.cn/s/blog_4155bb1d0100soq9.html INTEL MEDIA SDK是INTEL推出的基于其内建显示核心的编解码技术,我们在播放高清 ...

  5. FFmpeg源代码结构图 - 编码

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  6. Qt与FFmpeg联合开发指南(三)——编码(1):代码流程演示

    前两讲演示了基本的解码流程和简单功能封装,今天我们开始学习编码.编码就是封装音视频流的过程,在整个编码教程中,我会首先在一个函数中演示完成的编码流程,再解释其中存在的问题.下一讲我们会将编码功能进行封 ...

  7. FFmpeg源码结构图 - 编码

    ===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...

  8. X.509证书的编码及解析:程序解析以及winhex模板解析

    一.证书的整体结构:证书内容.签名算法.签名结果. 用ASN.1语法描述如下: Certificate::=SEQUENCE{ tbsCertificate TBSCertificate, signa ...

  9. h264 封装 RTMP中FLV数据的解析 rtmp协议简单解析以及用其发送h264的flv文件

    一个完整的多媒体文件是由音频和视频2部分组成的.H264.Xvid等就是视频编码格式,MP3.AAC等就是音频编码格式.字幕文件只是其中附带部分. 把视频编码和音频编码打包成一个完整的多媒体文件,可以 ...

随机推荐

  1. C语言-常量指针与指针常量

    最近倪健问我一个问题,他说:什么是常指针?什么是指向常变量的指针?请举例说明 我查阅资料后这么回答他了, 指针常量(常指针):int * const p : 指针是一个常量,也就是说它始终指向那个地址 ...

  2. java(数组及常用简单算法 )

    数组 数组:数组是存储同一种数据类型数据的集合容器. 数组的定义格式: 数据类型[]  变量名  =  new  数据类型[长度]; 数组的好处:对分配到数组对象中每一个数据都分配一个编号(索引值.角 ...

  3. vue 使用同一组件,切换时不触发created、mounted钩子

    两个页面参数不同使用同一组件,默认情况下当这两个页面切换时并不会触发created或者mounted钩子. 方法一:通过watch $route的变化来做处理 watch: { $route() { ...

  4. 什么是事件委托?jquery和js怎么去实现?

    事件委托又叫事件代理,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件. js: window.onload = function(){ var oul = docume ...

  5. Eclipse怎样把文件系统形式的项目作为工程直接导入?

    导入的时候,选择已经存在的工程,如果选择文件系统,可能会提示没有项目可以导入.这个时候,可以从其它Eclipse项目下,copy一份.project文件过来,修改源文件中的工程名字.如果需要,也可以c ...

  6. UVa 11743 - Credit Check

    题目:推断卡号是否合法,给你4组4位的数字.偶数位的2倍的位和加上奇数位的和,推断尾数是否为0. 分析:简单题,模拟. 直接依照提议推断就可以. 说明:460题,加油! #include <io ...

  7. c# 查询sql 返回多个參数

    1.依据须要查询mysql 语句,返回三个须要的參数,不是数据集 2.编写函数例如以下: public static void GetParas(string 条件1, out string 返回值1 ...

  8. Day2二分图笔记

    定义 左边一堆点 右边一堆点 树是一个二分图,奇数深度和偶数深度可以组成二分图, 二分图匹配 左边的点和右边的点有边 匈牙利算法 可能的答案 ans,n-ans,m-ans,n+m-ans  ||   ...

  9. powerdesigner 连接mysql提示“connection test failed”

    powerdesigner  连接mysql提示“connection test failed”,该如何解决: 1.把64位的jdk换成32位的jdk(VM只支持32的jre) 2.系统变量:  CL ...

  10. Objc执行时读取和写入plist文件遇到的问题

    以下是本猫保持游戏NPC和物件交互的plist文件: 随着游戏和玩家逐步发生互动,玩家会改动人物和物件的交互的状态.这也是RPG游戏最主要的功能. 在切换每一个地图时须要将上一个地图发生的改变存储到p ...