Opus in TS input/output support

This commit is contained in:
Thulinma 2020-06-16 19:32:59 +02:00
parent 1c47e9cdfc
commit 97752f2c2d
9 changed files with 240 additions and 27 deletions

View file

@ -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<long unsigned int>::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();

View file

@ -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:

View file

@ -8,6 +8,7 @@
#include <stdint.h>
#include <sys/stat.h>
#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;
}

View file

@ -18,7 +18,8 @@ namespace TS{
ID3 = 0x15,
MPEG2 = 0x02,
MP2 = 0x03,
META = 0x06
META = 0x06,
OPUS = 0x060001
};
class ADTSRemainder{

View file

@ -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] << " ]";

View file

@ -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;

View file

@ -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;

View file

@ -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://*");

View file

@ -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"){