|
本帖最后由 ancientcc 于 2020-10-12 14:16 编辑
webrtc如何解码rtsp,有种方案是在服务器把rtsp转成webrtc要求的格式,然后通过网络传给客户端,这里描述的方案是不通过服务器,客户端按收rtsp流,然后用通过webrtc解码,并渲染。
流程涉及到几个重要步骤及相关技术。
- 从网上收rtsp,并拆包成rtp。所用技术:live555。
- 解码rtsp收到的视频帧。所用技术:webrtc中的解码模块。
- 渲染解码出的数据。所用技术:从webrtc解码输出中取VideoFrame,即基于rtc::VideoSinkInterface::OnFrame获得解码帧。
当中较难的是第二步,要写出这一步的逻辑首先要清楚webrtc的解码流程。
一、webrtc的解码流程
- WebRtcVideoChannel::WebRtcVideoChannel中生成std::vector<VideoCodecSettings> recv_codecs;
- 枚举recv_codecs,每个单元生成std::unique_ptr<webrtc::VideoDecoder>。
new_decoder = decoder_factory_->CreateVideoDecoder(webrtc::SdpVideoFormat(recv_codec.codec.name, recv_codec.codec.params));
这个时候会生成H264DecoderImpl。生成的webrtc::VideoDecoder放在config_.decoders中。 - VideoReceiveStream::Start()时,枚举config_.decoders中的每个decoder,把decoder注册到video_receiver_的dec_external_map_和dec_map_
注册到dec_external_map_:video_receiver_.RegisterExternalDecoder(decoder.decoder, decoder.payload_type);
注册到dec_map_:video_receiver_.RegisterReceiveCodec(&codec, num_cpu_cores_, false)。当中codec是以decoder为参数,调用下面的语句生成。VideoCodec codec = CreateDecoderVideoCodec(decoder); - VideoReceiver:ecode在解码一帧前会调用_codecDataBase.GetDecoder去获取解码器,_codecDataBase类型是VCMCodecDataBase。于是可知,GetDecode即VCMCodecDataBase::GetDecoder。VCMCodecDataBase内部,用于存储h264Decoder的是一个类型是 std::unique_ptr<VCMGenericDecoder>变量ptr_decoder_,VCMGenericDecoder不和其它类有血缘关系,是单独存在。它和H264DecoderImpl发生关系是它内部有个类型是std::unique_ptr<VideoDecoder>的变量decoder_。以下是它的构造函数。
- VCMGenericDecoder::VCMGenericDecoder(VideoDecoder* decoder, bool isExternal);
复制代码
那ptr_decoder_是何时构建的?第一次调用GetDecoder时ptr_decoder_总是null,它使用第3步得到的dec_external_map_,如果dec_external_map_不支持解码该格式则使用dec_map_。对h264,dec_external_map_总是支持的,于是构建VCMGenericDecoder使用dec_external_map_。 - _codecDataBase.GetDecoder在构建出ptr_decoder_后就会调用H264DecoderImpl::InitDecode。综上所述,H264DecoderImpl的InitDecode、Decode都是在DecodingThread被调用。
二、解码流程中涉及到的几种类型
2.1 VideoDecoder
以下是H264DecoderImpl的血缘关系,下面在是上面的派生类。
- VideoDecoder
- H264Decoder
- H264DecoderImpl
复制代码
2.2 codecType和plType
- class VideoCodec {
- VideoCodecType codecType;
- unsigned char plType;
- };
- class VCMCodecDataBase {
- private:
- VideoCodec receive_codec_;
- std::unique_ptr<VCMGenericDecoder> ptr_decoder_;
- DecoderMap dec_map_;
- ExternalDecoderMap dec_external_map_;
- };
复制代码
VideoCodec中有两种type,codecType是从0开始的enum值,像kVideoCodecVP8、kVideoCodecH264。plType表示的是网上收到的数据类型,像100表示h264。VCMCodecDataBase要解码第一帧时,receive_codec_中的plType值是初始值,即0,发比较plType和网络数据的payload_type(100),发现不一样,于是就去找到解出该payload_type的解码器,并构建出ptr_decoder_。
给出payload_type,VCMCodecDataBase是如何创建ptr_decoder_?dec_map_、dec_external_map_的first就是payload_type。根据second创建出ptr_decoder_。创建ptr_decoder_,调用InitDecode后,会把dec_map_对应的VideoCodec赋值给VCMCodecDataBase的receive_codec_,于是解码下一帧时,receive_codec_中的plType等于网络数据的payload_type(100),可直接使用ptr_decoder_了。
可以这么认为,plType表示的是收到的数据的编码类型,有着标准定义。VideoCodecType是webrtc模块的自个定义。或许吧,将来会有另一外一套plType定义,但到了webrtc内部,它都统一到VideoCodecType。
三、生成同一编码格式的多个实例
webrtc内置了这么条规则:一个plType只能对应一个解码器实例。参考构建decoder实例过程,像dec_map_、dec_external_map_。这会给须要解码多路同一格式视频的app造成问题。
- int first_h264_payload_type_ = 200
- void trtspcapture::start_rtsp()
- {
- recv_codecs_ = cricket::AssignPayloadTypesAndDefaultCodecs(encoder_factory_->GetSupportedFormats());
- cricket::VideoCodec h264_codec;
- for (std::vector<cricket::VideoCodec>::iterator it = recv_codecs_.begin(); it != recv_codecs_.end(); ++ it) {
- cricket::VideoCodec& codec = *it;
- if (codec.name == cricket::kH264CodecName) {
- codec.id = first_h264_payload_type_;
- h264_codec = codec;
- break;
- }
- }
- h264_codec.id = first_h264_payload_type_ + 1;
- h264_codec.SetParam("rtsp", "ch1");
- recv_codecs_.push_back(h264_codec);
- ......
- }
复制代码 以上是“间接”让一个plType(h264=100)对应两个解码器实例。基本思路是1)找出想要多路的那个cricket::VideoCodec,克隆一个,并修改它的plType。2)为方便记住新plType,以及原有的那plType,顺便修改原来plType,让它们是连续值。3)修改新增cricket::VideoCodec的Param,这么做的原因是要让由它生成的SdpVideoFormat和原来不一样。
- webrtc::SdpVideoFormat video_format(codec.name, codec.params)
复制代码
四、plType在remote、local中的匹配过程
4.1 remote流
如何计算收到的payloadType?这个payloadType没有出现在编码器发送的码流,而在执行video_receiver_.Decode之前,会把收到的此帧(data, len)封装成一个RtspFrameObject对象(VCMEncodedFrame是它父类),在封装时“主观”给的一个值,对应_payloadType字段。目前这个值强制是200、201(at_=1)、依此类推。
4.2 local
比较是双方的,以上是remote“发来”的payloadType,而且是个“主观”值,本地如何能确保这个“主观值”有对应的解码器。这个看dec_map_。local在枚举出recv_codecs(std::vector<cricket::VideoCodec>)后,发现该VideoCodec的name是H264,则强制把这个VideoCodec的playloadType改为200。至此,以200在dec_map_搜出的正是第一个h264解码器。
这里有个问题,当系统枚举出多个name是H264的VideoCodec,200对应的是一个第一个h264。因为没有remote流与之匹配的payloadType,第二个或后面的其实不会被使用。实测下来,多个h264时,它们只是profile-level-id不同,举个例子,第一个是42001f,一个是42e01f,那这个值如何改变VideoDecoder行为?——至少在windows,好像没影响。而在iOS、Android,都是只枚举出一个h264。
如果要做完善,local搜到出多个h264时,需跟据remote流中的profile-level-id去搜recv_codecs,找到“最能解码”它的profile-level-id的VideoCodec。 |
|