转载:http://blog.csdn.net/ManagerUser/article/details/76087151
一、前言
SRS流媒体服务器支持rtmp协议,但是rtmp协议仅仅支持PC直播。移动端直播需要HLS协议,HLS协议是苹果公司开发出来的,用于移动端视频直播,Android也对HLS做了友好支持。所以,SRS流媒体服务器支持rtmp协议和hls协议,满足了PC和移动端直播要求。
HLS协议有两个关键文件:.m3u8文件和.ts文件:
- .m3u8文件:播放控制文件,存放了地址和播放参数。
- .ts文件:真正存储视频文件。
相关HLS协议详细说明参见:https://www.jianshu.com/p/2ce402a485ca。
SRS流媒体接受到通过rtmp传输协议传输的编码格式为H264/AAC(注意:HLS协议只支持Video编码:H264;Audio编码:AAC/mp3)音视频数据,进行切片成.m3u8文件和.ts文件,存储在磁盘或者内存当中(注意:一般为了提高cpu使用率,将.m3u8和.ts文件存储在内存中)。再通过nginx分发到端(注意:nginx工作目录要和存储.m3u8路径一致)。
HLS切片处理,其实就是ts编码的处理,通过将H264/AAC编码数据按照TS协议来分成一个一个的TS包。TS协议规定,TS首包内容为PAT(Program Association Table 节目关联表)表,其PID为0x0;接下来为PMT(Program Map Table 节目映射表)表,其PID为0x1001。其次,视频帧PID为:0x100,音频PID为:0x101。后面TS包为实际音视频数据。
二、代码分析
SRS源码相关其他总结:
SRS(simple-rtmp-server)流媒体服务器源码分析--系统启动
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
SRS(simple-rtmp-server)流媒体服务器源码分析--HLS切片
SRS源码HLS处理框架如下:
接着SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play分析,在接受到rtmp信息之后,进行HLS处理。
- int SrsSource::on_video_imp(SrsSharedPtrMessage* msg)
在函数中,除了转发给client和forward之外,还有HLS处理。注意:在HLS这一讲中,只分析跟video相关的代码,audio类同。
- #ifdef SRS_AUTO_HLS
- if ((ret = hls->on_video(msg, is_sequence_header)) != ERROR_SUCCESS) {
-
-
- std::string hls_error_strategy = _srs_config->get_hls_on_error(_req->vhost);
- if (srs_config_hls_is_on_error_ignore(hls_error_strategy)) {
- srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret);
-
- hls->on_unpublish();
-
- ret = ERROR_SUCCESS;
- } else if (srs_config_hls_is_on_error_continue(hls_error_strategy)) {
- if (srs_hls_can_continue(ret, cache_sh_video, msg)) {
- ret = ERROR_SUCCESS;
- } else {
- srs_warn("hls continue video failed. ret=%d", ret);
- return ret;
- }
- } else {
- srs_warn("hls disconnect publisher for video error. ret=%d", ret);
- return ret;
- }
- }
- #endif
进入on_video(),
- int SrsHls::on_video(SrsSharedPtrMessage* shared_video, bool is_sps_pps)
- {
- int ret = ERROR_SUCCESS;
-
- if (!hls_enabled) {
- return ret;
- }
-
-
- last_update_time = srs_get_system_time_ms();
-
- SrsSharedPtrMessage* video = shared_video->copy();
- SrsAutoFree(SrsSharedPtrMessage, video);
-
-
-
-
-
-
-
-
-
- if (is_sps_pps) {
- codec->avc_parse_sps = _srs_config->get_parse_sps(_req->vhost);
- }
-
- sample->clear();
-
- if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
- srs_error("hls codec demux video failed. ret=%d", ret);
- return ret;
- }
- srs_info("video decoded, type=%d, codec=%d, avc=%d, cts=%d, size=%d, time=%"PRId64,
- sample->frame_type, codec->video_codec_id, sample->avc_packet_type, sample->cts, video->size, video->timestamp);
-
-
-
- if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) {
- return ret;
- }
-
- if (codec->video_codec_id != SrsCodecVideoAVC) {
- return ret;
- }
-
-
- if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame
- && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
- return hls_cache->on_sequence_header(muxer);
- }
-
-
- if ((ret = jitter->correct(video, SrsRtmpJitterAlgorithmOFF)) != ERROR_SUCCESS) {
- srs_error("rtmp jitter correct video failed. ret=%d", ret);
- return ret;
- }
-
-
-
-
-
-
-
- int64_t dts = video->timestamp * 90;
- stream_dts = dts;
- if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
- srs_error("hls cache write video failed. ret=%d", ret);
- return ret;
- }
-
-
- hls_show_mux_log();
-
- return ret;
- }
在该函数中做了几个工作:
1、获取H264编码信息SPS和PPS,注意:SPS和PPS不是每个流中都会有的,它只包含在头帧当中。
2、检测视频压缩编码格式:必须为H264,否则直接退出。
3、RTMP抖动矫正(不清楚做了些什么事,不管了)
4、HLS切片处理。
以上4点,前三点不在这里详细说明。只看HLS切片部分:
- int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample)
- {
- int ret = ERROR_SUCCESS;
-
-
- if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
- return ret;
- }
-
-
- if (muxer->is_segment_overflow()) {
-
-
-
- if (!muxer->wait_keyframe() || sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) {
-
- if ((ret = reap_segment("video", muxer, cache->video->dts)) != ERROR_SUCCESS) {
- return ret;
- }
- }
- }
-
-
- if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) {
- srs_error("m3u8 muxer flush video failed. ret=%d", ret);
- return ret;
- }
-
- return ret;
- }
该函数做一下两点工作:
1、首次或者.ts文件时间溢出时,进入reap_segment()函数,该函数主要负责.m3u8和.ts文件管理(创建,打开,关闭)。注意:.m3u8文件是在ts文件写入完毕之后,在关闭操作中一次性将播放参数,ts地址等等参数写入。
2、其他时间,直接进入后面flush_video()函数,该函数负责ts流编码和.ts文件写入。
其中ts流编码设计到很多知识,大家自行查看相关知识:http://blog.csdn.net/u013354805/article/details/51578457
- int SrsHlsMuxer::flush_video(SrsTsCache* cache)
- {
- int ret = ERROR_SUCCESS;
-
-
- if (!current) {
- srs_warn("flush video ignored, for segment is not open.");
- return ret;
- }
-
- if (!cache->video || cache->video->payload->length() <= 0) {
- return ret;
- }
-
- srs_assert(current);
-
-
- current->update_duration(cache->video->dts);
-
- if ((ret = current->muxer->write_video(cache->video)) != ERROR_SUCCESS) {
- return ret;
- }
-
-
- srs_freep(cache->video);
-
- return ret;
- }
进入编码函数
- int SrsTsContext::encode(SrsFileWriter* writer, SrsTsMessage* msg, SrsCodecVideo vc, SrsCodecAudio ac)
- {
- int ret = ERROR_SUCCESS;
-
- SrsTsStream vs, as;
- int16_t video_pid = 0, audio_pid = 0;
- switch (vc) {
- case SrsCodecVideoAVC:
- vs = SrsTsStreamVideoH264;
- video_pid = TS_VIDEO_AVC_PID;
- break;
- case SrsCodecVideoDisabled:
- vs = SrsTsStreamReserved;
- break;
- case SrsCodecVideoReserved:
- case SrsCodecVideoReserved1:
- case SrsCodecVideoReserved2:
- case SrsCodecVideoSorensonH263:
- case SrsCodecVideoScreenVideo:
- case SrsCodecVideoOn2VP6:
- case SrsCodecVideoOn2VP6WithAlphaChannel:
- case SrsCodecVideoScreenVideoVersion2:
- vs = SrsTsStreamReserved;
- break;
- }
- switch (ac) {
- case SrsCodecAudioAAC:
- as = SrsTsStreamAudioAAC;
- audio_pid = TS_AUDIO_AAC_PID;
- break;
- case SrsCodecAudioMP3:
- as = SrsTsStreamAudioMp3;
- audio_pid = TS_AUDIO_MP3_PID;
- break;
- case SrsCodecAudioDisabled:
- as = SrsTsStreamReserved;
- break;
- case SrsCodecAudioReserved1:
- case SrsCodecAudioLinearPCMPlatformEndian:
- case SrsCodecAudioADPCM:
- case SrsCodecAudioLinearPCMLittleEndian:
- case SrsCodecAudioNellymoser16kHzMono:
- case SrsCodecAudioNellymoser8kHzMono:
- case SrsCodecAudioNellymoser:
- case SrsCodecAudioReservedG711AlawLogarithmicPCM:
- case SrsCodecAudioReservedG711MuLawLogarithmicPCM:
- case SrsCodecAudioReserved:
- case SrsCodecAudioSpeex:
- case SrsCodecAudioReservedMP3_8kHz:
- case SrsCodecAudioReservedDeviceSpecificSound:
- as = SrsTsStreamReserved;
- break;
- }
-
- if (as == SrsTsStreamReserved && vs == SrsTsStreamReserved) {
- ret = ERROR_HLS_NO_STREAM;
- srs_error("hls: no video or audio stream, vcodec=%d, acodec=%d. ret=%d", vc, ac, ret);
- return ret;
- }
-
-
- if (vcodec != vc || acodec != ac) {
- vcodec = vc;
- acodec = ac;
- if ((ret = encode_pat_pmt(writer, video_pid, vs, audio_pid, as)) != ERROR_SUCCESS) {
- return ret;
- }
- }
-
-
- if (msg->is_audio()) {
- return encode_pes(writer, msg, audio_pid, as, vs == SrsTsStreamReserved);
- } else {
- return encode_pes(writer, msg, video_pid, vs, vs == SrsTsStreamReserved);
- }
- }
该函数内容有点多:
1、根据音视频类型,获取不同的PID(在TS协议当中,TS包包含了音视频数据,它们是通过TS头协议PID来区分的)
2、TS编码,PAT帧,PMT帧(首帧TS流)
3、TS编码,音视频数据编码(TS流数据部分)
可以先进入encode_pat_pmt()函数函数中看看:
- int SrsTsContext::encode_pat_pmt(SrsFileWriter* writer, int16_t vpid, SrsTsStream vs, int16_t apid, SrsTsStream as)
- {
- int ret = ERROR_SUCCESS;
-
- if (vs != SrsTsStreamVideoH264 && as != SrsTsStreamAudioAAC && as != SrsTsStreamAudioMp3) {
- ret = ERROR_HLS_NO_STREAM;
- srs_error("hls: no pmt pcr pid, vs=%d, as=%d. ret=%d", vs, as, ret);
- return ret;
- }
-
- int16_t pmt_number = TS_PMT_NUMBER;
- int16_t pmt_pid = TS_PMT_PID;
- if (true) {
-
- SrsTsPacket* pkt = SrsTsPacket::create_pat(this, pmt_number, pmt_pid);
- SrsAutoFree(SrsTsPacket, pkt);
-
- char* buf = new char[SRS_TS_PACKET_SIZE];
- SrsAutoFreeA(char, buf);
-
-
- int nb_buf = pkt->size();
- srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
- memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
-
- SrsStream stream;
- if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
- return ret;
- }
-
- if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
- srs_error("ts encode ts packet failed. ret=%d", ret);
- return ret;
- }
- if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
- srs_error("ts write ts packet failed. ret=%d", ret);
- return ret;
- }
- }
- if (true) {
-
- SrsTsPacket* pkt = SrsTsPacket::create_pmt(this, pmt_number, pmt_pid, vpid, vs, apid, as);
- SrsAutoFree(SrsTsPacket, pkt);
-
- char* buf = new char[SRS_TS_PACKET_SIZE];
- SrsAutoFreeA(char, buf);
-
-
- int nb_buf = pkt->size();
- srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
- memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
-
- SrsStream stream;
- if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
- return ret;
- }
-
- if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
- srs_error("ts encode ts packet failed. ret=%d", ret);
- return ret;
- }
-
- if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
- srs_error("ts write ts packet failed. ret=%d", ret);
- return ret;
- }
- }
-
-
- ready = true;
-
- return ret;
- }
上面代码创建PAT和PMT两个TS包。将TS文件前两个包解析,如下:
再来看encode_pes()函数
- int SrsTsContext::encode_pes(SrsFileWriter* writer, SrsTsMessage* msg, int16_t pid, SrsTsStream sid, bool pure_audio)
- {
- int ret = ERROR_SUCCESS;
-
-
- if (!ready) {
- ret = ERROR_TS_CONTEXT_NOT_READY;
- srs_error("TS: context not ready, ret=%d", ret);
- return ret;
- }
-
- if (msg->payload->length() == 0) {
- return ret;
- }
-
- if (sid != SrsTsStreamVideoH264 && sid != SrsTsStreamAudioMp3 && sid != SrsTsStreamAudioAAC) {
- srs_info("ts: ignore the unknown stream, sid=%d", sid);
- return ret;
- }
-
- SrsTsChannel* channel = get(pid);
- srs_assert(channel);
-
- char* start = msg->payload->bytes();
- char* end = start + msg->payload->length();
- char* p = start;
-
- while (p < end) {
- SrsTsPacket* pkt = NULL;
- if (p == start) {
-
- bool write_pcr = msg->write_pcr;
-
-
-
- if (pure_audio && msg->is_audio()) {
- write_pcr = true;
- }
-
-
-
-
-
-
-
-
-
-
-
- int64_t pcr = write_pcr? msg->dts : -1;
-
-
- pkt = SrsTsPacket::create_pes_first(this,
- pid, msg->sid, channel->continuity_counter++, msg->is_discontinuity,
- pcr, msg->dts, msg->pts, msg->payload->length()
- );
- } else {
- pkt = SrsTsPacket::create_pes_continue(this,
- pid, msg->sid, channel->continuity_counter++
- );
- }
- SrsAutoFree(SrsTsPacket, pkt);
-
- char* buf = new char[SRS_TS_PACKET_SIZE];
- SrsAutoFreeA(char, buf);
-
-
- int nb_buf = pkt->size();
- srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
-
- int left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf);
- int nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left;
- if (nb_stuffings > 0) {
-
- memset(buf, 0xFF, SRS_TS_PACKET_SIZE);
-
-
- pkt->padding(nb_stuffings);
-
-
- nb_buf = pkt->size();
- srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
-
- left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf);
- nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left;
- srs_assert(nb_stuffings == 0);
- }
- memcpy(buf + nb_buf, p, left);
- p += left;
-
- SrsStream stream;
- if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
- return ret;
- }
- if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
- srs_error("ts encode ts packet failed. ret=%d", ret);
- return ret;
- }
- if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
- srs_error("ts write ts packet failed. ret=%d", ret);
- return ret;
- }
- }
-
- return ret;
- }
编码成TS包,写入TS文件。
三、总结
1、TS编码,实际是按照TS协议来重新整理数据,TS协议是传输协议。TS协议传输的音视频数据压缩格式还是H264/AAC。
2、TS切片,实际是按照TS协议要求的字段长度对音视频数据进行分包处理。
3、TS文件,TS包存储的位置。又涉及到HLS协议和TS协议关系。