From 97752f2c2d50ecd8d4a0bbf524e80753d3169bba Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 16 Jun 2020 19:32:59 +0200 Subject: [PATCH] Opus in TS input/output support --- lib/ts_packet.cpp | 142 ++++++++++++++++++++++++++++++---- lib/ts_packet.h | 3 + lib/ts_stream.cpp | 94 ++++++++++++++++++++-- lib/ts_stream.h | 3 +- src/analysers/analyser_ts.cpp | 4 + src/input/input_ts.cpp | 1 + src/output/output_httpts.cpp | 1 + src/output/output_ts.cpp | 1 + src/output/output_ts_base.cpp | 18 ++++- 9 files changed, 240 insertions(+), 27 deletions(-) diff --git a/lib/ts_packet.cpp b/lib/ts_packet.cpp index e93040ee..4ca06cd2 100644 --- a/lib/ts_packet.cpp +++ b/lib/ts_packet.cpp @@ -584,6 +584,35 @@ namespace TS{ } return tmpStr; } + + /// Generates a PES Lead-in for a meta frame. + /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data. + /// \param len The length of this frame. + /// \param PTS The timestamp of the frame. + std::string &Packet::getPESPS1LeadIn(unsigned int len, unsigned long long PTS, uint64_t bps){ + if (bps >= 50){ + len += 3; + }else{ + bps = 0; + } + static std::string tmpStr; + tmpStr.clear(); + tmpStr.reserve(20); + len += 8; + tmpStr.append("\000\000\001\275", 4); + tmpStr += (char)((len & 0xFF00) >> 8); // PES PacketLength + tmpStr += (char)(len & 0x00FF); // PES PacketLength (Cont) + tmpStr += (char)0x84; // isAligned + tmpStr += (char)(0x80 | (bps ? 0x10 : 0)); // PTS/DTS + Flags + tmpStr += (char)(5 + (bps ? 3 : 0)); // PESHeaderDataLength + encodePESTimestamp(tmpStr, 0x20, PTS); + if (bps){ + char rate_buf[3]; + Bit::htob24(rate_buf, (bps / 50) | 0x800001); + tmpStr.append(rate_buf, 3); + } + return tmpStr; + } // END PES FUNCTIONS /// Fills the free bytes of the Packet. @@ -1081,11 +1110,70 @@ namespace TS{ return "und"; } + /// Returns the registration field, or an empty string if none present. + std::string ProgramDescriptors::getRegistration() const{ + for (uint32_t p = 0; p + 1 < p_len; p += p_data[p + 1] + 2){ + if (p_data[p] == 0x05){// registration + return std::string(p_data + p + 2, 4); + } + } + // No tag found! Empty string. + return ""; + } + + /// Returns the extension field, or an empty string if none present. + std::string ProgramDescriptors::getExtension() const{ + for (uint32_t p = 0; p + 1 < p_len; p += p_data[p + 1] + 2){ + if (p_data[p] == 0x7F){// extension + return std::string(p_data + p + 2, p_data[p+1]); + } + } + // No tag found! Empty string. + return ""; + } + /// Prints all descriptors we understand in a readable format, the others in raw hex. std::string ProgramDescriptors::toPrettyString(size_t indent) const{ std::stringstream output; for (uint32_t p = 0; p + 1 < p_len; p += p_data[p + 1] + 2){ switch (p_data[p]){ + case 0x05:{// registration descriptor + if ((p_data[p+2] >= 32 && p_data[p+2] <= 126) && + (p_data[p+2] >= 32 && p_data[p+2] <= 126) && + (p_data[p+2] >= 32 && p_data[p+2] <= 126) && + (p_data[p+2] >= 32 && p_data[p+2] <= 126)){ + //printable ASCII + output << std::string(indent, ' ') << "Registration: " << std::string(p_data + p + 2, 4); + //Extra binary data, if any + if (p_data[p+1] > 4){ + output << " ("; + for (uint32_t i = 6; i < p_data[p + 1] + 2; ++i){ + output << std::hex << std::setw(2) << std::setfill('0') << std::uppercase + << (int)p_data[p + i] << std::dec; + } + output << ")"; + } + output << std::endl; + }else{ + output << std::string(indent, ' ') << "Registration (unprintable): "; + for (uint32_t i = 2; i < p_data[p + 1] + 2; ++i){ + output << std::hex << std::setw(2) << std::setfill('0') << std::uppercase + << (int)p_data[p + i] << std::dec; + } + output << std::endl; + } + }break; + case 0x06:{// data alignment descriptor + output << std::string(indent, ' ') << "Data alignment: "; + switch (p_data[p+2]){ + case 1: output << "Slice or Video Access Unit (1)"; break; + case 2: output << "Video Access Unit (2)"; break; + case 3: output << "GOP or SEQ (3)"; break; + case 4: output << "SEQ (4)"; break; + default: output << "Reserved (" << (int)(p_data[p+2]) << ")"; break; + } + output << std::endl; + }break; case 0x0A:{// ISO 639-2 language tag (ISO 13818-1) uint32_t offset = 0; while (offset < p_data[p + 1]){// single language @@ -1146,6 +1234,22 @@ namespace TS{ } } }break; + case 0x7F:{// extension descriptor + output << std::string(indent, ' ') << "Extension: "; + if (p_data[p+2] < 0x80){ + output << "Unimplemented"; + }else{ + output << "User defined"; + } + output << " ("; + output << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << (int)p_data[p + 2] << std::dec; + output << ") - "; + for (uint32_t i = 3; i < p_data[p + 1] + 2; ++i){ + output << std::hex << std::setw(2) << std::setfill('0') << std::uppercase + << (int)p_data[p + i] << std::dec; + } + output << std::endl; + }break; case 0x52:{// DVB stream identifier output << std::string(indent, ' ') << "Stream identifier: Tag #" << (int)p_data[p + 2] << std::endl; }break; @@ -1186,12 +1290,11 @@ namespace TS{ std::string codec = M.getCodec(*it); sectionLen += 5; if (codec == "ID3" || codec == "RAW"){sectionLen += M.getInit(*it).size();} - if (codec == "AAC"){ - sectionLen += 4; // aac descriptor - std::string lang = M.getLang(*it); - if (lang.size() == 3 && lang != "und"){ - sectionLen += 6; // language descriptor - } + if (codec == "AAC"){sectionLen += 4;} // length of AAC descriptor + if (codec == "opus"){sectionLen += 10;} // 6 bytes registration desc, 4 bytes opus desc + std::string lang = M.getLang(*it); + if (lang.size() == 3 && lang != "und"){ + sectionLen += 6; // language descriptor } } PMT.setSectionLength(0xB00D + sectionLen); @@ -1215,7 +1318,7 @@ namespace TS{ for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ std::string codec = M.getCodec(*it); entry.setElementaryPid(getUniqTrackID(M, *it)); - entry.setESInfo(""); + std::string es_info; if (codec == "H264"){ entry.setStreamType(0x1B); }else if (codec == "HEVC"){ @@ -1224,18 +1327,15 @@ namespace TS{ entry.setStreamType(0x02); }else if (codec == "AAC"){ entry.setStreamType(0x0F); - std::string aac_info("\174\002\121\000", - 4); // AAC descriptor: AAC Level 2. Hardcoded, because... what are AAC levels, anyway? - // language code ddescriptor - std::string lang = M.getLang(*it); - if (lang.size() == 3 && lang != "und"){ - aac_info.append("\012\004", 2); - aac_info.append(lang); - aac_info.append("\000", 1); - } - entry.setESInfo(aac_info); + // AAC descriptor: AAC Level 2. Hardcoded, because... what are AAC levels, anyway? + es_info.append("\174\002\121\000", 4); }else if (codec == "MP3" || codec == "MP2"){ entry.setStreamType(0x03); + }else if (codec == "opus"){ + entry.setStreamType(0x06); + es_info.append("\005\004Opus", 6);//registration descriptor + es_info.append("\177\002\200", 3);//Opus descriptor + es_info.append(1, (char)M.getChannels(*it)); }else if (codec == "AC3"){ entry.setStreamType(0x81); }else if (codec == "ID3"){ @@ -1245,6 +1345,14 @@ namespace TS{ entry.setStreamType(0x06); entry.setESInfo(M.getInit(*it)); } + // language code descriptor + std::string lang = M.getLang(*it); + if (lang.size() == 3 && lang != "und"){ + es_info.append("\012\004", 2); + es_info.append(lang); + es_info.append("\000", 1); + } + entry.setESInfo(es_info); entry.advance(); } PMT.calcCRC(); diff --git a/lib/ts_packet.h b/lib/ts_packet.h index e3ab320d..eeb09309 100644 --- a/lib/ts_packet.h +++ b/lib/ts_packet.h @@ -76,6 +76,7 @@ namespace TS{ unsigned long long offset, bool isAligned, uint64_t bps = 0); static std::string &getPESAudioLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); static std::string &getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); + static std::string &getPESPS1LeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); // Printers and writers std::string toPrettyString(size_t indent = 0, int detailLevel = 3) const; @@ -111,6 +112,8 @@ namespace TS{ public: ProgramDescriptors(const char *data, const uint32_t len); std::string getLanguage() const; + std::string getRegistration() const; + std::string getExtension() const; std::string toPrettyString(size_t indent) const; private: diff --git a/lib/ts_stream.cpp b/lib/ts_stream.cpp index 115c8b84..c1eec5bd 100644 --- a/lib/ts_stream.cpp +++ b/lib/ts_stream.cpp @@ -8,6 +8,7 @@ #include #include #include "tinythread.h" +#include "opus.h" tthread::recursive_mutex tMutex; @@ -206,12 +207,19 @@ namespace TS{ case ID3: case MP2: case MPEG2: - case META: + case OPUS: + case META:{ pidToCodec[pid] = sType; - if (sType == ID3 || sType == META){ - metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength()); + std::string & init = metaInit[pid]; + init.assign(entry.getESInfo(), entry.getESInfoLength()); + if (sType == META){ + TS::ProgramDescriptors desc(init.data(), init.size()); + std::string reg = desc.getRegistration(); + if (reg == "Opus"){ + pidToCodec[pid] = OPUS; + } } - break; + } break; default: break; } entry.advance(); @@ -547,7 +555,36 @@ namespace TS{ mp2Hdr[tid] = std::string(pesPayload, realPayloadSize); } } - + if (thisCodec == OPUS){ + size_t offset = 0; + while (realPayloadSize > offset+1){ + size_t headSize = 2; + size_t packSize = 0; + bool control_ext = false; + if (pesPayload[offset+1] & 0x10){headSize += 2;}//trim start + if (pesPayload[offset+1] & 0x08){headSize += 2;}//trim end + if (pesPayload[offset+1] & 0x04){control_ext = true;}//control extension + while (pesPayload[offset+2] == 255){ + packSize += 255; + ++offset; + } + packSize += pesPayload[offset+2]; + ++offset; + offset += headSize; + //Skip control extension, if present + if (control_ext){ + offset += pesPayload[offset] + 1; + } + if (realPayloadSize < offset+packSize){ + WARN_MSG("Encountered invalid Opus frame (%zu > %zu) - discarding!", offset+packSize, realPayloadSize); + break; + } + out.push_back(DTSC::Packet()); + out.back().genericFill(timeStamp, timeOffset, tid, pesPayload+offset, packSize, bPos, 0); + timeStamp += Opus::Opus_getDuration(pesPayload+offset); + offset += packSize; + } + } if (thisCodec == H264 || thisCodec == H265){ const char *nextPtr; const char *pesEnd = pesPayload + realPayloadSize; @@ -915,6 +952,52 @@ namespace TS{ codec = "AC3"; size = 16; }break; + case OPUS:{ + addNewTrack = true; + type = "audio"; + codec = "opus"; + size = 16; + init = std::string("OpusHead\001\002\170\000\200\273\000\000\000\000\001", 19); + channels = 2; + std::string extData = TS::ProgramDescriptors(metaInit[it->first].data(), metaInit[it->first].size()).getExtension(); + if (extData.size() > 1){ + channels = extData[1]; + uint8_t channel_map = extData[2]; + if (channels > 8){ + FAIL_MSG("Channel count %u not implemented", (int)channels); + if (channel_map == 1){channel_map = 255;} + } + if (channel_map > 1){ + FAIL_MSG("Channel mapping table %u not implemented", (int)init[18]); + channel_map = 255; + } + if (channels > 2 && channels <= 8 && channel_map == 0){ + WARN_MSG("Overriding channel mapping table from 0 to 1"); + channel_map = 1; + } + init[9] = channels; + init[18] = channel_map; + if (channel_map == 1){ + static const uint8_t opus_coupled_stream_cnt[9] = {1,0,1,1,2,2,2,3,3}; + static const uint8_t opus_stream_cnt[9] = {1,1,1,2,2,3,4,4,5}; + static const uint8_t opus_channel_map[8][8] = { + {0}, + {0,1}, + {0,2,1}, + {0,1,2,3}, + {0,4,1,2,3}, + {0,4,1,2,3,5}, + {0,4,1,2,3,5,6}, + {0,6,1,2,3,4,5,7}, + }; + init += (char)opus_stream_cnt[channels]; + init += (char)opus_coupled_stream_cnt[channels]; + init += std::string("\000\000\000\000\000\000\000\000", channels); + memcpy((char*)init.data()+21, opus_channel_map[channels - 1], channels); + } + } + rate = 48000; + }break; case MP2:{ addNewTrack = true; Mpeg::MP2Info info = Mpeg::parseMP2Header(mp2Hdr[it->first]); @@ -996,6 +1079,7 @@ namespace TS{ case ID3: case MP2: case MPEG2: + case OPUS: case META: result.insert(entry.getElementaryPid()); break; default: break; } diff --git a/lib/ts_stream.h b/lib/ts_stream.h index 4f9e41b7..bdefb3c7 100644 --- a/lib/ts_stream.h +++ b/lib/ts_stream.h @@ -18,7 +18,8 @@ namespace TS{ ID3 = 0x15, MPEG2 = 0x02, MP2 = 0x03, - META = 0x06 + META = 0x06, + OPUS = 0x060001 }; class ADTSRemainder{ diff --git a/src/analysers/analyser_ts.cpp b/src/analysers/analyser_ts.cpp index 4f31aef9..7c405e9c 100644 --- a/src/analysers/analyser_ts.cpp +++ b/src/analysers/analyser_ts.cpp @@ -94,6 +94,10 @@ std::string AnalyserTS::printPES(const std::string &d, size_t PID){ res << " [Audio " << (int)(d[3] & 0x1F) << "]"; known = true; } + if (!known && d[3] == 0xBD){ + res << " [Private Stream 1]"; + known = true; + } if (!known){res << " [Unknown stream ID: " << (int)d[3] << "]";} if (d[0] != 0 || d[1] != 0 || d[2] != 1){ res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]"; diff --git a/src/input/input_ts.cpp b/src/input/input_ts.cpp index 4ff692f5..f0e58ba9 100644 --- a/src/input/input_ts.cpp +++ b/src/input/input_ts.cpp @@ -172,6 +172,7 @@ namespace Mist{ capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("MP2"); + capa["codecs"][0u][1u].append("opus"); inFile = NULL; inputProcess = 0; isFinished = false; diff --git a/src/output/output_httpts.cpp b/src/output/output_httpts.cpp index 740a37f0..ddf9ee6f 100644 --- a/src/output/output_httpts.cpp +++ b/src/output/output_httpts.cpp @@ -70,6 +70,7 @@ namespace Mist{ capa["codecs"][0u][1u].append("+MP3"); capa["codecs"][0u][1u].append("+AC3"); capa["codecs"][0u][1u].append("+MP2"); + capa["codecs"][0u][1u].append("+opus"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/mpeg"; capa["methods"][0u]["priority"] = 1; diff --git a/src/output/output_ts.cpp b/src/output/output_ts.cpp index 6c097b67..efcc643c 100644 --- a/src/output/output_ts.cpp +++ b/src/output/output_ts.cpp @@ -110,6 +110,7 @@ namespace Mist{ capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("MP2"); + capa["codecs"][0u][1u].append("opus"); cfg->addConnectorOptions(8888, capa); config = cfg; capa["push_urls"].append("tsudp://*"); diff --git a/src/output/output_ts_base.cpp b/src/output/output_ts_base.cpp index c244fa57..94a3b551 100644 --- a/src/output/output_ts_base.cpp +++ b/src/output/output_ts_base.cpp @@ -164,11 +164,21 @@ namespace Mist{ packTime = aacSamples * 90000 / freq; } } - bs = TS::Packet::getPESAudioLeadIn(tempLen, packTime, M.getBps(thisIdx)); - fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); - if (codec == "AAC"){ - bs = TS::getAudioHeader(dataLen, M.getInit(thisIdx)); + if (codec == "opus"){ + tempLen += 3 + (dataLen/255); + bs = TS::Packet::getPESPS1LeadIn(tempLen, packTime, M.getBps(thisIdx)); fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); + bs = "\177\340"; + bs.append(dataLen/255, (char)255); + bs.append(1, (char)(dataLen-255*(dataLen/255))); + fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); + }else{ + bs = TS::Packet::getPESAudioLeadIn(tempLen, packTime, M.getBps(thisIdx)); + fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); + if (codec == "AAC"){ + bs = TS::getAudioHeader(dataLen, M.getInit(thisIdx)); + fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); + } } fillPacket(dataPointer, dataLen, firstPack, video, keyframe, pkgPid, contPkg); }else if (type == "meta"){