Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42371
  • 博文数量: 30
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2020-10-13 16:47
文章分类

全部博文(30)

文章存档

2020年(30)

我的朋友

分类: LINUX

2020-10-13 16:56:25

原文地址:硬件编解码 作者:BinChengfei

INPUT模块是指通过各种方式(如读本地文件,http,udp,rtsp等等)将数据写到一段内存中,DEMUX模块只负责从相应的内存中取数据,也就是INPUT在一个线程,DEMUX在另外的线程里。
FFMPEG的架构是DEMUX需要数据时,先访问Buffer2,Buffer2如果有数据,则直接使用,如果没有,则调用INPUT模块的api,读数据并将数据写入到Buffer1,然后将Buffer1的数据memcpy到Buffer2。Buffer1的初始化在 av_open_input_file函数的url_fopen的url_fdopen函数处理的,普通大小是1M;而Buffer2的大小是根据 DEMUX要求临时生成的buffer。向Buffer1写数据依赖于URLProtocol结构体,先看看该结构体的定义。
typedef struct URLProtocol
{
    const char *name;
    int (*url_open)(URLContext *h, const char *filename, int flags);
    int (*url_read)(URLContext *h, unsigned char *buf, int size);
    int (*url_write)(URLContext *h, unsigned char *buf, int size);
    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
    int (*url_close)(URLContext *h);
    struct URLProtocol *next;
    int (*url_read_pause)(URLContext *h, int pause);
    int64_t (*url_read_seek)(URLContext *h, int stream_index,
                             int64_t timestamp, int flags);
} URLProtocol;
函数指针url_open是根据当前name进行open的,如果的本地文件,则直接open该文件,如果是http,则需要进行socket通信,并判断socket是否成功建立;url_read,将数据读入buf所指向的内存中,url_write将数据写入buf,url_seek,seek到指定的地方;url_close:realease open时相关的资源。
[attach]2[/attach]
为了INPUT从FFMPEG分开,且符合FFMPEG架构,则构建了如下模型。
[attach]3[/attach]
  Buffer1和Buffer2的关系不变,变的是向Buffer1写数据模块,因此增加了一个新的URLProtocol类型,
URLProtocol mem_protocol =
{
    "mem",
    mem_data_open,
    mem_data_read,
    mem_data_write,
    mem_data_seek,
    mem_data_close,
};
而且需要在ffmpeg/libavformat/allformat.c 中注册REGISTER_PROTOCOL(MEM,mem);依次实现mem_data_open、mem_data_read、 mem_data_write、mem_data_seek、mem_data_close。
这样FFMPEG只需从Buffer3获取数据,而不用关心数据以何种方式得到
 

demux和decoder的分离是指将demux出来的数据通过适配层后送给硬件decode,因为HW可以直接解码。

  这里记录一下当初遇到的问题。

       从av_read_frame出来的码流,一般来讲,已经是比较正规的ES流,通过适配层后送入解码器。所谓适配层是指该平台提供的ES解码所需要的header。

1, mp4/mkv/mov/flv封装的h.264

  mp4/mkv/mov/flv,我称之为特殊容器,因为这些容器为了减少存储,减少了一些头信息。

  这种容器封装的h.264需要判断ES流中是否包含pps和sps。如果包含,万事大吉,直接送入解码器,如果不包含,则需要进行一些处理。首先是,将 sps和pps加入到ES流的前端,然后对av_read_frame出来的ES流进行处理。因为nal单元的起始码是0x000001,此时获得的ES 流前4个byte是当前nal的大小,所以将ES数据正规化,然后送入解码器。

2,特殊容器的 AAC;

  由于特殊容器的AAC 不是ADTS,因此你需要加入7个bytes的ADTS的头变成ADTS。ADTS header 规范如下:

  syncword:12 bits; always: '111111111111'
  ID:             1 bits;   0: MPEG-4, 1: MPEG-2
  layer:         2 bits ; always: '00'
  protection_absent: 1 bits
  profile: 2 bits
  sampling_frequency_index: 4 bits
  private_bit: 1 bits
  channel_configuration: 3 bits
  original/copy: 1 bits
  home: 1 bits

   copyright_identification_bit 1 bits
       copyright_identification_start 1 bits
       aac_frame_length 13 length of the frame including header (in bytes)
       adts_buffer_fullness 11 0x7FF indicates VBR
       no_raw_data_blocks_in_frame 2 bits


       ADTS Error check
       crc_check 16 only if protection_absent == 0
       After that come (no_raw_data_blocks_in_frame+1) raw_data_blocks.
       Some elaborations:
       profile
       bits ID == 1 (MPEG-2 profile) ID == 0 (MPEG-4 Object type)
      00 (0) Main profile AAC MAIN
      01 (1) Low Complexity profile (LC) AAC LC
      10 (2) Scalable Sample Rate profile (SSR) AAC SSR
      11 (3) (reserved) AAC LTP  

    

对于TS,m2ts,mts,由于是标准编码,因此一般由硬件直接demux、decode。



0x000001是标志位,必须有的,所以必须加上,mov/mp4/flv容器分离出来的ES流前面四个bytes是当前frame的长度,将它设为 0x00 00 00 01就行。如果还有问题,你可以将能解的h.263容器转成mp4的,然后再分离出ES流,看两种容器分离出来的同一个h.263有什么区别,比较一下应该就知道怎么处理了。

10楼的朋友,我也遇到你说的情况,就是从mp4分离出来的开始不能播,后来仔细分析发现从ffmpeg av_read_frame读出来的帧数据实际上有好几个帧,我把这里面的每一个帧都加上0x000001之后再送入解码器,就正常了。
但是我现在用的最新版ffmpeg,在分离AAC时总是提示“AOT unsupported。。。”这种错误,导致拿到的AAC的objtype,samplerate和channelconf是错的。不知道各位有没有遇到这个问题?能否分享一下经验?


前面写了两篇blog,介绍mfc的hw decoder加入到mplayer和vlc的事情,由于平时懒散惯了,很少总结完整的技术文档,只是收到的邮件太多,我有空都尽量及时回复,只是问的邮件多了,自然很多问题都是重复的,总是回复类似的问题,实在太枯燥,也没那么多时间.现在随机选几个代表性的回复,贴在这里,给需要的朋友参考!
Q:在mplayer那部分代码加入S3C6410的MFC
A:我提供一种方式:
1.在etc/codecs.conf里面添加自己的decoder
2.编写vd_s3c**.c,格式可以参考 vd_rawvideo.c和vd_real.c
3.将vd_s3c**加入到vd.c里面
4.vo部分可以添加 vo_s3c**.c
5.将vo_s3c*加入到video_out.c
具体流程可以阅读mplayer源码DOC/tech目录下的文档

Q:我H264的video stream 同样是baseline的,我封装了一个文件,
不能解出来, 可能是格式上有出入,但我对H264不是很了解。
我真正要用的是,从网络上获取了H264 的video stream后,用S3C 6410 的MFC 来解码。
但我不知道怎么下手做了
A:仔细看下MFC的那几个文档就可以了,文件封装的H264可能需要自己做demuxer,那个文档里面里面有提,ring buf模式支持文件封装的数据,我没测试,linebuf只支持标准的es流,我是用mplayer把文件demuxer出来的h264数据送到 linebuf模式的mfc,这样来测试的。
网络上取H264数据,是rtp/udp还是http?反正都是文件封装的问题,最好用基本的es 流,如果没有文件封装,那需要在本地解析组装NAL

Q:我收到的buffer 根本就没有传给MFC 的 line_buffer. 这个我不明白 ,我也是按MFC 的测试程序做的。 因为给line_buffer 的数据为空,所以一直就没P,P,…… I, P…… 进去,解出来就是花的。我初始化是在一个函数里,解码是在别一个函数里
A:decoder是从pStrmBuf获取H.264的数据,然后把pYUVBuf里面的yuv数据取出来显示,所以你每次都要更新pStrmBuf
更新pStrmBuf的方法可以自己实现,也可以用mfc实例里面现成的NextFrameH264函数:
使用NextFrameH264函数的时候,需要设置源数据(H.264)的的起始地址p_start和结束地址p_end,所以在每次调用NextFrameH264函数之前都需要下面这段代码重新设置p_start和p_end
        pFrameExCtx = FrameExtractorInit(FRAMEX_IN_TYPE_MEM, delimiter_h264, sizeof(delimiter_h264), 1);
        file_strm.p_start = file_strm.p_cur = (unsigned char *)data;
        file_strm.p_end = (unsigned char *)(data + len);
        FrameExtractorFirst(pFrameExCtx, &file_strm);
所以正确的顺序应该这样:
先设置 p_start,p_end,然后调用NextFrameH264更新pStrmBuf,然后再decode,再取yuv
-->set_pos(p_start,end)->update_pStrmBuf(NextFrameH264)-->decode->display->...

你的代码结构里面会遇到一个问题,就是开始初始化的时候的H.264源数据会没有播放,所以影片开头的一些画面可能会看不到,你可以根据自己的实际程序结构组织下,比如mfc的测试代码是每次播放完之后就预取下帧数据,所以他的代码是这样的结构:
init->--/|\->decode->display->getNextFrame---|
            |---<----------------------------------|
你也可以自己组织其他模式,比如他还支持decoder和 diaplayer的同步,所以就可以用多线程模式来提高效率

Q:我看了MFC测试程序的代码,他把SPS帧和第一个I帧之间的P帧给丢弃掉了,这个是正确的处理方法吗?
A:mfc初始化的时候必须要按照他那个文档说的,就是config stream(pps,sps,sei,I frame)这样初始化成功后,才可以送入p帧或者I帧正常解码,如果没有完整的config stream,只能在整个数据里面搜索,直到成功搜索到为止,具体可以看mfc的实例代码,他里面也应该是这样的实现。
0001是nal的 startcode,查看实际的h264码流,有时候也可能是001,所以这时候要具体分析下,大致就是如此

Q:是否有S3C.C的原碼可以供參考 ,謝謝
A:不提供源码.下面是大概的实现思路
s3c.c是按照vlc codec接口标准自己写的代码
也就是用mfc的api实现一个符合vlc codec接口
mfc 的api实现方法可以看mfc demo
vlc codec接口可以参考vlc代码目录下codec/rawvideo.c以及codec/realvideo.c的实现
模仿而已
OpenDecoder
CloseDecoder
DecodeBlock
需要实现类似这样的函数
还可以去看看vlc官方文档
http://wiki.videolan.org/Documentation:Hacker%27s_Guide/Decoder
阅读(895) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~