Ability to send silence to RTMP outputs
This commit is contained in:
parent
dc2239e70f
commit
99907782ae
6 changed files with 131 additions and 29 deletions
|
@ -464,28 +464,22 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack){
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FLV Audio init data loader function from metadata.
|
/// 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;
|
len = 0;
|
||||||
// Unknown? Assume AAC.
|
if (codec == "AAC"){len = initData.size() + 17;}
|
||||||
if (meta.getCodec(aTrack) == "?"){meta.setCodec(aTrack, "AAC");}
|
|
||||||
std::string initData = meta.getInit(aTrack);
|
|
||||||
if (meta.getCodec(aTrack) == "AAC"){len = initData.size() + 17;}
|
|
||||||
if (len <= 0 || !checkBufferSize()){return false;}
|
if (len <= 0 || !checkBufferSize()){return false;}
|
||||||
memcpy(data + 13, initData.c_str(), len - 17);
|
memcpy(data + 13, initData.c_str(), len - 17);
|
||||||
data[12] = 0; // AAC sequence header
|
data[12] = 0; // AAC sequence header
|
||||||
data[11] = 0;
|
data[11] = 0xA0;
|
||||||
if (meta.getCodec(aTrack) == "AAC"){data[11] += 0xA0;}
|
if (sampleRate >= 44100){
|
||||||
if (meta.getCodec(aTrack) == "MP3"){data[11] += 0x20;}
|
data[11] |= 0x0C;
|
||||||
unsigned int datarate = meta.getRate(aTrack);
|
}else if (sampleRate >= 22050){
|
||||||
if (datarate >= 44100){
|
data[11] |= 0x08;
|
||||||
data[11] += 0x0C;
|
}else if (sampleRate >= 11025){
|
||||||
}else if (datarate >= 22050){
|
data[11] |= 0x04;
|
||||||
data[11] += 0x08;
|
|
||||||
}else if (datarate >= 11025){
|
|
||||||
data[11] += 0x04;
|
|
||||||
}
|
}
|
||||||
if (meta.getSize(aTrack) != 8){data[11] += 0x02;}
|
if (sampleSize != 8){data[11] += 0x02;}
|
||||||
if (meta.getChannels(aTrack) > 1){data[11] += 0x01;}
|
if (channels > 1){data[11] += 0x01;}
|
||||||
setLen();
|
setLen();
|
||||||
data[0] = 0x08;
|
data[0] = 0x08;
|
||||||
data[1] = ((len - 15) >> 16) & 0xFF;
|
data[1] = ((len - 15) >> 16) & 0xFF;
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace FLV{
|
||||||
bool ChunkLoader(const RTMPStream::Chunk &O);
|
bool ChunkLoader(const RTMPStream::Chunk &O);
|
||||||
bool DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx);
|
bool DTSCLoader(DTSC::Packet &packData, const DTSC::Meta &M, size_t idx);
|
||||||
bool DTSCVideoInit(DTSC::Meta &meta, uint32_t vTrack);
|
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<long unsigned int> &selTracks);
|
bool DTSCMetaInit(const DTSC::Meta &M, std::set<long unsigned int> &selTracks);
|
||||||
void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage);
|
void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage);
|
||||||
void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack, const std::map<std::string, std::string> &targetParams);
|
void toMeta(DTSC::Meta &meta, AMF::Object &amf_storage, size_t &reTrack, const std::map<std::string, std::string> &targetParams);
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace Mist{
|
||||||
if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){
|
if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){
|
||||||
myConn.SendNow(tag.data, tag.len);
|
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);
|
myConn.SendNow(tag.data, tag.len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace Mist{
|
||||||
if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){
|
if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){
|
||||||
myConn.SendNow(tag.data, tag.len);
|
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);
|
myConn.SendNow(tag.data, tag.len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,7 +281,7 @@ namespace Mist{
|
||||||
H.Chunkify("\000\000\000\000mdat", 8, myConn);
|
H.Chunkify("\000\000\000\000mdat", 8, myConn);
|
||||||
// send init data, if needed.
|
// send init data, if needed.
|
||||||
if (audioTrack != INVALID_TRACK_ID && M.getInit(audioTrack) != ""){
|
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);
|
tag.tagTime(mstime);
|
||||||
H.Chunkify(tag.data, tag.len, myConn);
|
H.Chunkify(tag.data, tag.len, myConn);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
namespace Mist{
|
namespace Mist{
|
||||||
OutRTMP::OutRTMP(Socket::Connection &conn) : Output(conn){
|
OutRTMP::OutRTMP(Socket::Connection &conn) : Output(conn){
|
||||||
|
lastSilence = 0;
|
||||||
|
hasSilence = false;
|
||||||
lastAck = Util::bootSecs();
|
lastAck = Util::bootSecs();
|
||||||
lastOutTime = 0;
|
lastOutTime = 0;
|
||||||
setRtmpOffset = false;
|
setRtmpOffset = false;
|
||||||
|
@ -256,6 +258,87 @@ namespace Mist{
|
||||||
"push out, when pushing out.\"}"));
|
"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(){
|
void OutRTMP::sendNext(){
|
||||||
// If there are now more selectable tracks, select the new track and do a seek to the current
|
// 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
|
// timestamp Set sentHeader to false to force it to send init data
|
||||||
|
@ -283,6 +366,29 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
lastOutTime = thisPacket.getTime() - rtmpOffset;
|
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
|
char rtmpheader[] ={0, // byte 0 = cs_id | ch_type
|
||||||
0, 0, 0, // bytes 1-3 = timestamp
|
0, 0, 0, // bytes 1-3 = timestamp
|
||||||
|
@ -361,13 +467,6 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
data_len += dheader_len;
|
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);
|
bool allow_short = RTMPStream::lastsend.count(4);
|
||||||
RTMPStream::Chunk &prev = RTMPStream::lastsend[4];
|
RTMPStream::Chunk &prev = RTMPStream::lastsend[4];
|
||||||
uint8_t chtype = 0x00;
|
uint8_t chtype = 0x00;
|
||||||
|
@ -485,9 +584,15 @@ namespace Mist{
|
||||||
if (tag.DTSCVideoInit(meta, *it)){myConn.SendNow(RTMPStream::SendMedia(tag));}
|
if (tag.DTSCVideoInit(meta, *it)){myConn.SendNow(RTMPStream::SendMedia(tag));}
|
||||||
}
|
}
|
||||||
if (type == "audio"){
|
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;
|
sentHeader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ namespace Mist{
|
||||||
uint64_t lastAck;
|
uint64_t lastAck;
|
||||||
HTTP::URL pushApp, pushUrl;
|
HTTP::URL pushApp, pushUrl;
|
||||||
uint8_t authAttempts;
|
uint8_t authAttempts;
|
||||||
|
void sendSilence(uint64_t currTime);
|
||||||
|
bool hasSilence;
|
||||||
|
uint64_t lastSilence;
|
||||||
};
|
};
|
||||||
}// namespace Mist
|
}// namespace Mist
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue