From 99907782ae28be8f389cbbb4eda9319d32b1becf Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 24 Jul 2020 15:29:32 +0200 Subject: [PATCH] Ability to send silence to RTMP outputs --- lib/flv_tag.cpp | 28 ++++----- lib/flv_tag.h | 2 +- src/output/output_flv.cpp | 4 +- src/output/output_hds.cpp | 2 +- src/output/output_rtmp.cpp | 121 ++++++++++++++++++++++++++++++++++--- src/output/output_rtmp.h | 3 + 6 files changed, 131 insertions(+), 29 deletions(-) diff --git a/lib/flv_tag.cpp b/lib/flv_tag.cpp index 432ab869..80bd72be 100644 --- a/lib/flv_tag.cpp +++ b/lib/flv_tag.cpp @@ -464,28 +464,22 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack){ } /// FLV Audio init data loader function from metadata. -bool FLV::Tag::DTSCAudioInit(DTSC::Meta &meta, uint32_t aTrack){ +bool FLV::Tag::DTSCAudioInit(const std::string & codec, unsigned int sampleRate, unsigned int sampleSize, unsigned int channels, const std::string & initData){ len = 0; - // Unknown? Assume AAC. - if (meta.getCodec(aTrack) == "?"){meta.setCodec(aTrack, "AAC");} - std::string initData = meta.getInit(aTrack); - if (meta.getCodec(aTrack) == "AAC"){len = initData.size() + 17;} + if (codec == "AAC"){len = initData.size() + 17;} if (len <= 0 || !checkBufferSize()){return false;} memcpy(data + 13, initData.c_str(), len - 17); data[12] = 0; // AAC sequence header - data[11] = 0; - if (meta.getCodec(aTrack) == "AAC"){data[11] += 0xA0;} - if (meta.getCodec(aTrack) == "MP3"){data[11] += 0x20;} - unsigned int datarate = meta.getRate(aTrack); - if (datarate >= 44100){ - data[11] += 0x0C; - }else if (datarate >= 22050){ - data[11] += 0x08; - }else if (datarate >= 11025){ - data[11] += 0x04; + data[11] = 0xA0; + if (sampleRate >= 44100){ + data[11] |= 0x0C; + }else if (sampleRate >= 22050){ + data[11] |= 0x08; + }else if (sampleRate >= 11025){ + data[11] |= 0x04; } - if (meta.getSize(aTrack) != 8){data[11] += 0x02;} - if (meta.getChannels(aTrack) > 1){data[11] += 0x01;} + if (sampleSize != 8){data[11] += 0x02;} + if (channels > 1){data[11] += 0x01;} setLen(); data[0] = 0x08; data[1] = ((len - 15) >> 16) & 0xFF; diff --git a/lib/flv_tag.h b/lib/flv_tag.h index 54527615..bea495ea 100644 --- a/lib/flv_tag.h +++ b/lib/flv_tag.h @@ -51,7 +51,7 @@ namespace FLV{ bool ChunkLoader(const RTMPStream::Chunk &O); bool DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx); bool DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack); - bool DTSCAudioInit(DTSC::Meta &meta, uint32_t aTrack); + bool DTSCAudioInit(const std::string & codec, unsigned int sampleRate, unsigned int sampleSize, unsigned int channels, const std::string & initData); bool DTSCMetaInit(const DTSC::Meta &M, std::set &selTracks); void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage); void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack, const std::map &targetParams); diff --git a/src/output/output_flv.cpp b/src/output/output_flv.cpp index dc1af181..0d4a51a8 100644 --- a/src/output/output_flv.cpp +++ b/src/output/output_flv.cpp @@ -64,7 +64,7 @@ namespace Mist{ if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){ myConn.SendNow(tag.data, tag.len); } - if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta, it->first)){ + if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta.getCodec(it->first), meta.getRate(it->first), meta.getSize(it->first), meta.getChannels(it->first), meta.getInit(it->first))){ myConn.SendNow(tag.data, tag.len); } } @@ -117,7 +117,7 @@ namespace Mist{ if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){ myConn.SendNow(tag.data, tag.len); } - if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta, *it)){ + if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta.getCodec(*it), meta.getRate(*it), meta.getSize(*it), meta.getChannels(*it), meta.getInit(*it))){ myConn.SendNow(tag.data, tag.len); } } diff --git a/src/output/output_hds.cpp b/src/output/output_hds.cpp index bafcd60e..d9401896 100644 --- a/src/output/output_hds.cpp +++ b/src/output/output_hds.cpp @@ -281,7 +281,7 @@ namespace Mist{ H.Chunkify("\000\000\000\000mdat", 8, myConn); // send init data, if needed. if (audioTrack != INVALID_TRACK_ID && M.getInit(audioTrack) != ""){ - if (tag.DTSCAudioInit(meta, audioTrack)){ + if (tag.DTSCAudioInit(meta.getCodec(audioTrack), meta.getRate(audioTrack), meta.getSize(audioTrack), meta.getChannels(audioTrack), meta.getInit(audioTrack))){ tag.tagTime(mstime); H.Chunkify(tag.data, tag.len, myConn); } diff --git a/src/output/output_rtmp.cpp b/src/output/output_rtmp.cpp index 4522e41f..adc01592 100644 --- a/src/output/output_rtmp.cpp +++ b/src/output/output_rtmp.cpp @@ -13,6 +13,8 @@ namespace Mist{ OutRTMP::OutRTMP(Socket::Connection &conn) : Output(conn){ + lastSilence = 0; + hasSilence = false; lastAck = Util::bootSecs(); lastOutTime = 0; setRtmpOffset = false; @@ -256,6 +258,87 @@ namespace Mist{ "push out, when pushing out.\"}")); } + void OutRTMP::sendSilence(uint64_t timestamp){ + const char * tmpData = "\257\001!\020\004`\214\034"; + size_t data_len = 8; + + char rtmpheader[] ={0, // byte 0 = cs_id | ch_type + 0, 0, 0, // bytes 1-3 = timestamp + 0, 0, 0, // bytes 4-6 = length + 0x08, // byte 7 = msg_type_id + 1, 0, 0, 0, // bytes 8-11 = msg_stream_id = 1 + 0, 0, 0, 0}; // bytes 12-15 = extended timestamp + bool allow_short = RTMPStream::lastsend.count(4); + RTMPStream::Chunk &prev = RTMPStream::lastsend[4]; + uint8_t chtype = 0x00; + size_t header_len = 12; + bool time_is_diff = false; + if (allow_short && (prev.cs_id == 4)){ + if (prev.msg_stream_id == 1){ + chtype = 0x40; + header_len = 8; // do not send msg_stream_id + if (data_len == prev.len && rtmpheader[7] == prev.msg_type_id){ + chtype = 0x80; + header_len = 4; // do not send len and msg_type_id + if (timestamp == prev.timestamp){ + chtype = 0xC0; + header_len = 1; // do not send timestamp + } + } + // override - we always sent type 0x00 if the timestamp has decreased since last chunk in this channel + if (timestamp < prev.timestamp){ + chtype = 0x00; + header_len = 12; + }else{ + // store the timestamp diff instead of the whole timestamp + timestamp -= prev.timestamp; + time_is_diff = true; + } + } + } + + // update previous chunk variables + prev.cs_id = 4; + prev.msg_stream_id = 1; + prev.len = data_len; + prev.msg_type_id = 0x08; + if (time_is_diff){ + prev.timestamp += timestamp; + }else{ + prev.timestamp = timestamp; + } + + // cs_id and ch_type + rtmpheader[0] = chtype | 4; + // data length, 3 bytes + rtmpheader[4] = (data_len >> 16) & 0xff; + rtmpheader[5] = (data_len >> 8) & 0xff; + rtmpheader[6] = data_len & 0xff; + // timestamp, 3 bytes + if (timestamp >= 0x00ffffff){ + // send extended timestamp + rtmpheader[1] = 0xff; + rtmpheader[2] = 0xff; + rtmpheader[3] = 0xff; + rtmpheader[header_len++] = (timestamp >> 24) & 0xff; + rtmpheader[header_len++] = (timestamp >> 16) & 0xff; + rtmpheader[header_len++] = (timestamp >> 8) & 0xff; + rtmpheader[header_len++] = timestamp & 0xff; + }else{ + // regular timestamp + rtmpheader[1] = (timestamp >> 16) & 0xff; + rtmpheader[2] = (timestamp >> 8) & 0xff; + rtmpheader[3] = timestamp & 0xff; + } + + // send the packet + myConn.setBlocking(true); + myConn.SendNow(rtmpheader, header_len); + myConn.SendNow(tmpData, data_len); + RTMPStream::snd_cnt += header_len+data_len; // update the sent data counter + myConn.setBlocking(false); + } + void OutRTMP::sendNext(){ // If there are now more selectable tracks, select the new track and do a seek to the current // timestamp Set sentHeader to false to force it to send init data @@ -283,6 +366,29 @@ namespace Mist{ } lastOutTime = thisPacket.getTime() - rtmpOffset; } + uint64_t timestamp = thisPacket.getTime() - rtmpOffset; + // make sure we don't go negative + if (rtmpOffset > (int64_t)thisPacket.getTime()){ + timestamp = 0; + rtmpOffset = (int64_t)thisPacket.getTime(); + } + + //Send silence packets if needed + if (hasSilence){ + //If there's more than 15s of skip, skip audio as well + if (timestamp > 15000 && lastSilence < timestamp - 15000){ + lastSilence = timestamp - 30; + } + //convert time to packet counter + uint64_t currSilence = ((lastSilence*44100+512000)/1024000)+1; + uint64_t silentTime = currSilence*1024000/44100; + //keep sending silent packets until we've caught up to the current timestamp + while (silentTime < timestamp){ + sendSilence(silentTime); + lastSilence = silentTime; + silentTime = (++currSilence)*1024000/44100; + } + } char rtmpheader[] ={0, // byte 0 = cs_id | ch_type 0, 0, 0, // bytes 1-3 = timestamp @@ -361,13 +467,6 @@ namespace Mist{ } data_len += dheader_len; - uint64_t timestamp = thisPacket.getTime() - rtmpOffset; - // make sure we don't go negative - if (rtmpOffset > (int64_t)thisPacket.getTime()){ - timestamp = 0; - rtmpOffset = (int64_t)thisPacket.getTime(); - } - bool allow_short = RTMPStream::lastsend.count(4); RTMPStream::Chunk &prev = RTMPStream::lastsend[4]; uint8_t chtype = 0x00; @@ -485,9 +584,15 @@ namespace Mist{ if (tag.DTSCVideoInit(meta, *it)){myConn.SendNow(RTMPStream::SendMedia(tag));} } if (type == "audio"){ - if (tag.DTSCAudioInit(meta, *it)){myConn.SendNow(RTMPStream::SendMedia(tag));} + if (tag.DTSCAudioInit(meta.getCodec(*it), meta.getRate(*it), meta.getSize(*it), meta.getChannels(*it), meta.getInit(*it))){myConn.SendNow(RTMPStream::SendMedia(tag));} } } + //Insert silent init data if audio set to silent + hasSilence = (targetParams.count("audio") && targetParams["audio"] == "silent"); + if (hasSilence && tag.DTSCAudioInit("AAC", 44100, 32, 2, std::string("\022\020V\345\000", 5))){ + INFO_MSG("Inserting silence track init data"); + myConn.SendNow(RTMPStream::SendMedia(tag)); + } sentHeader = true; } diff --git a/src/output/output_rtmp.h b/src/output/output_rtmp.h index cdc72b0e..63a324df 100644 --- a/src/output/output_rtmp.h +++ b/src/output/output_rtmp.h @@ -32,6 +32,9 @@ namespace Mist{ uint64_t lastAck; HTTP::URL pushApp, pushUrl; uint8_t authAttempts; + void sendSilence(uint64_t currTime); + bool hasSilence; + uint64_t lastSilence; }; }// namespace Mist