Opus in TS input/output support
This commit is contained in:
parent
1c47e9cdfc
commit
97752f2c2d
9 changed files with 240 additions and 27 deletions
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ namespace TS{
|
|||
ID3 = 0x15,
|
||||
MPEG2 = 0x02,
|
||||
MP2 = 0x03,
|
||||
META = 0x06
|
||||
META = 0x06,
|
||||
OPUS = 0x060001
|
||||
};
|
||||
|
||||
class ADTSRemainder{
|
||||
|
|
|
@ -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] << " ]";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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://*");
|
||||
|
|
|
@ -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"){
|
||||
|
|
Loading…
Add table
Reference in a new issue