x264编码得到的文件,在文件头部的一些东西,有了新发现
转载自:
http://wmnmtm.blog.163.com/blog/static/38245714201192211245389/
猜想:
下载的一个源码里,实现了把264文件通过RTP发送,但是如果发送实时编码的怎么办,序列参数集和图像参数集得自己发送,因为264文件本身在文件开头已经存储了这些了。
在x264.dsw工程中,找了一下,找到点相关的东西。
另外,x264.exe在编码文件时,只有一个地方进行文件的写入,就是
int write_nalu_bsf( hnd_t handle, uint8_t *p_nalu, int i_size )
{
if (fwrite(p_nalu, i_size, 1, (FILE *)handle) > 0)
{
return i_size;
}
return -1;
}
如果把函数里的代码注释掉,会发现,虽然在不停的编码,但最终文件大小始终为0,从这一点可以证明只有此处进行文件的写入操作。
那么,序列参数集是哪里来的呢,又找到一部分东西,在encoder.c文件的函数:
int x264_encoder_encode( x264_t *h,/* 指定编码器 */
x264_nal_t **pp_nal, /* x264_nal_t * */
int *pi_nal,
/* int */
x264_picture_t *pic_in,
x264_picture_t *pic_out )
中,有如下代码:
/* Write SPS and PPS 写序列参数集、图像参数集以及SEI版本信息,并不是写入文件,而是写入输出缓冲区 最后通过p_write_nalu中的fwrite写入到文件*/
if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers )
{
printf("encoder.c : Write SPS and PPS");
system("pause");//暂停,任意键继续
if( h->fenc->i_frame == 0 )
{
/* identify ourself */
x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
x264_sei_version_write( h, &h->out.bs );
x264_nal_end( h );
}
/* generate sequence parameters */
x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );
x264_sps_write( &h->out.bs, h->sps );
x264_nal_end( h );
/* generate picture parameters */
x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );
x264_pps_write( &h->out.bs, h->pps );
x264_nal_end( h );
}
/* Write frame 写入帧,并不是写入文件,而是写入输出缓冲区*/
i_frame_size = x264_slices_write( h );
在图一中,看到有一个网址http://www.videolan.org,在源码中搜索这个网址,找到唯一的一处,也就是如下的函数:
void x264_sei_version_write( x264_t *h, bs_t *s )
{
int i;
// random ID number generated according to ISO-11578
const uint8_t uuid[16] = {
0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7,
0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef
};
char version[1200];
int length;
char *opts = x264_param2string( &h->param, 0 );
sprintf( version, "x264 - core %d%s - H.264/MPEG-4 AVC codec - "
"
Copyleft 2005 - http://www.videolan.org/x264.html - options: %s",
X264_BUILD, X264_VERSION, opts );
x264_free( opts );
length = strlen(version)+1+16;
bs_write( s, 8, 0x5 ); // payload_type = user_data_unregistered
// payload_size
for( i = 0; i <= length-255; i += 255 )
bs_write( s, 8, 255 );
bs_write( s, 8, length-i );
for( i = 0; i < 16; i++ )
bs_write( s, 8, uuid[i] );
for( i = 0; i < length-16; i++ )
bs_write( s, 8, version[i] );
bs_rbsp_trailing( s );
}
(图三)注释掉x264_pps_write( &h->out.bs, h->pps );的情况
如果是实时的话,可以模仿下面的这两句代码发送序列参数集和图像参数集:
x264_sps_write( &h->out.bs, h->sps );
x264_pps_write( &h->out.bs, h->pps );
实际就是要发送h->sps和h->pps
因为我编码的文件是可以播放的,所以实际已经产生了sps和pps,直接发送就行了。
因为h是已知的,h = x264_encoder_open( ¶m );它的返回值就是x264_t,这样就可以通过h->sps和h->pps,下面是我将它们单独写入文件的截图:
上面是一个sps(sps1,sps_2,sps_3),看着内容好少啊。写入代码是:file1.Write(h->sps,sizeof(x264_sps_t));
PPS的内容也好少,可能是因为大多数均未指定,采用的是默认参数的原因吧,但是图像宽度和高度在哪儿了。
h->x264_param_t param;
这个也很关键,因为我指定的都是在这个里,
param.i_width=320;
param.i_height=240;
把这个也保一份看看。
h->PARAM
param.i_width=320;
param.i_height=240;
这两个数字对应的在哪呢,应该有才对啊,找找。(其实已经有了,只是现在没找到,看后面)
后来换了个参数值,又试了一下
param.i_width=320;
param.i_height=240;
这是i_width 为320和100的比较,一直折腾半天,发现0x64能对应上十进制100,但是320怎么也对不上。
后来又从结构体的存储上找了半天(我直观的认为结构体中的变量字段是按顺序存在一块连续空间的,对不对不知道了)
我分析,在unsigned int 和int共占8字节,那么第9、10、11、12个字节应该就是i_width,但还是对不上。直到ing提到小头和大头,也就是主机字节序和网络字节序时,才惊醒过来。
原来,前面一直是对的,只是自己看成是错的了,呵呵。
0x 64 00 ,实际应该看成 0x 00 64 ,换算成二进制表示: 0000 0000 0110 0100
0x 40 01 ,
实际应该看成 0x 01 40 ,换算成二进制表示: 0000 0001 0100 0000
这下就对了。
小结一下:
1、结构体的各字段存储在一段连续的内存中。
2、主机字节序,用二进制查看器查看到 0x 64 00 ,换算二进制时,要按 0x 00 64这样反过来。(说法不标准,呵呵)
3、x264.exe编码得到的文件,前面有一段版权声明,是没用的。
4、file1.Write(&(h->param),sizeof(x264_param_t));这样把h->param写入二进制文件是正确的。
5、只发送sps和pps可以吗