299 lines
11 KiB
C++
299 lines
11 KiB
C++
#include "output_hls.h"
|
|
#include <mist/defines.h>
|
|
#include <mist/http_parser.h>
|
|
#include <mist/stream.h>
|
|
#include <unistd.h>
|
|
|
|
namespace Mist {
|
|
///\brief Builds an index file for HTTP Live streaming.
|
|
///\return The index file for HTTP Live Streaming.
|
|
std::string OutHLS::liveIndex(){
|
|
std::stringstream result;
|
|
result << "#EXTM3U\r\n";
|
|
int audioId = -1;
|
|
std::string audioName;
|
|
for (std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
|
if (it->second.codec == "AAC"){
|
|
audioId = it->first;
|
|
audioName = it->second.getIdentifier();
|
|
break;
|
|
}
|
|
}
|
|
for (std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
|
if (it->second.codec == "H264"){
|
|
int bWidth = it->second.bps * 2;
|
|
if (audioId != -1){
|
|
bWidth += myMeta.tracks[audioId].bps * 2;
|
|
}
|
|
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << bWidth * 10 << "\r\n";
|
|
result << it->first;
|
|
if (audioId != -1){
|
|
result << "_" << audioId;
|
|
}
|
|
result << "/index.m3u8\r\n";
|
|
}
|
|
}
|
|
#if DEBUG >= 8
|
|
std::cerr << "Sending this index:" << std::endl << result.str() << std::endl;
|
|
#endif
|
|
return result.str();
|
|
}
|
|
|
|
std::string OutHLS::liveIndex(int tid){
|
|
updateMeta();
|
|
std::stringstream result;
|
|
//parse single track
|
|
int longestFragment = 0;
|
|
if (!myMeta.tracks[tid].fragments.size()){
|
|
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
|
return "";
|
|
}
|
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++){
|
|
if (it->getDuration() > longestFragment){
|
|
longestFragment = it->getDuration();
|
|
}
|
|
}
|
|
result << "#EXTM3U\r\n"
|
|
"#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n"
|
|
"#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags << "\r\n";
|
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++){
|
|
if (myMeta.live && myMeta.tracks[tid].fragments.size() > 2 && it == myMeta.tracks[tid].fragments.begin()){
|
|
//skip the first fragment if live and there are more than 2 fragments.
|
|
continue;
|
|
}
|
|
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
|
|
|
if (it != (myMeta.tracks[tid].fragments.end() - 1)){
|
|
result << "#EXTINF:" << ((it->getDuration() + 500) / 1000) << ", no desc\r\n" << starttime << "_" << it->getDuration() + starttime << ".ts\r\n";
|
|
}
|
|
}
|
|
if ( !myMeta.live){
|
|
result << "#EXT-X-ENDLIST\r\n";
|
|
}
|
|
#if DEBUG >= 8
|
|
std::cerr << "Sending this index:" << std::endl << result.str() << std::endl;
|
|
#endif
|
|
return result.str();
|
|
} //liveIndex
|
|
|
|
|
|
OutHLS::OutHLS(Socket::Connection & conn) : Output(conn) {
|
|
haveAvcc = false;
|
|
myConn.setHost(config->getString("ip"));
|
|
streamName = config->getString("streamname");
|
|
}
|
|
|
|
OutHLS::~OutHLS() {}
|
|
|
|
void OutHLS::onFail(){
|
|
HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers
|
|
HTTP_S.SetBody("Stream not found. Sorry, we tried.");
|
|
HTTP_S.SendResponse("404", "Stream not found", myConn);
|
|
Output::onFail();
|
|
}
|
|
|
|
void OutHLS::init(Util::Config * cfg){
|
|
Output::init(cfg);
|
|
capa["name"] = "HLS";
|
|
capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
|
|
capa["deps"] = "HTTP";
|
|
capa["url_rel"] = "/hls/$/index.m3u8";
|
|
capa["url_prefix"] = "/hls/$/";
|
|
capa["socket"] = "http_hls";
|
|
capa["codecs"][0u][0u].append("H264");
|
|
capa["codecs"][0u][1u].append("AAC");
|
|
capa["methods"][0u]["handler"] = "http";
|
|
capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl";
|
|
capa["methods"][0u]["priority"] = 9ll;
|
|
cfg->addBasicConnectorOptions(capa);
|
|
config = cfg;
|
|
}
|
|
|
|
void OutHLS::sendNext(){
|
|
Socket::Buffer ToPack;
|
|
char * ContCounter = 0;
|
|
bool IsKeyFrame = false;
|
|
|
|
char * dataPointer = 0;
|
|
unsigned int dataLen = 0;
|
|
currentPacket.getString("data", dataPointer, dataLen);
|
|
|
|
if (currentPacket.getTime() >= until){
|
|
DEBUG_MSG(DLVL_DEVEL, "(%d) Done sending fragment", getpid() );
|
|
stop();
|
|
wantRequest = true;
|
|
HTTP_S.Chunkify("", 0, myConn);
|
|
HTTP_S.Clean();
|
|
return;
|
|
}
|
|
|
|
//detect packet type, and put converted data into ToPack.
|
|
if (myMeta.tracks[currentPacket.getTrackId()].type == "video"){
|
|
ToPack.append(TS::Packet::getPESVideoLeadIn(0ul, currentPacket.getTime() * 90));
|
|
|
|
IsKeyFrame = currentPacket.getInt("keyframe");
|
|
if (IsKeyFrame){
|
|
if (!haveAvcc){
|
|
avccbox.setPayload(myMeta.tracks[currentPacket.getTrackId()].init);
|
|
haveAvcc = true;
|
|
}
|
|
ToPack.append(avccbox.asAnnexB());
|
|
}
|
|
unsigned int i = 0;
|
|
while (i + 4 < (unsigned int)dataLen){
|
|
unsigned int ThisNaluSize = (dataPointer[i] << 24) + (dataPointer[i+1] << 16) + (dataPointer[i+2] << 8) + dataPointer[i+3];
|
|
if (ThisNaluSize + i + 4 > (unsigned int)dataLen){
|
|
DEBUG_MSG(DLVL_WARN, "Too big NALU detected (%u > %d) - skipping!", ThisNaluSize + i + 4, dataLen);
|
|
break;
|
|
}
|
|
ToPack.append("\000\000\000\001", 4);
|
|
i += 4;
|
|
ToPack.append(dataPointer + i, ThisNaluSize);
|
|
i += ThisNaluSize;
|
|
}
|
|
ContCounter = &VideoCounter;
|
|
}else if (myMeta.tracks[currentPacket.getTrackId()].type == "audio"){
|
|
if (AppleCompat){
|
|
ToPack.append(TS::Packet::getPESAudioLeadIn(7+dataLen, lastVid));
|
|
}else{
|
|
ToPack.append(TS::Packet::getPESAudioLeadIn(7+dataLen, currentPacket.getTime() * 90));
|
|
}
|
|
ToPack.append(TS::GetAudioHeader(dataLen, myMeta.tracks[currentPacket.getTrackId()].init));
|
|
ToPack.append(dataPointer, dataLen);
|
|
ContCounter = &AudioCounter;
|
|
}
|
|
|
|
bool first = true;
|
|
//send TS packets
|
|
while (ToPack.size()){
|
|
if (PacketNumber % 42 == 0){
|
|
HTTP_S.Chunkify(TS::PAT, 188, myConn);
|
|
HTTP_S.Chunkify(TS::PMT, 188, myConn);
|
|
PacketNumber += 2;
|
|
}
|
|
PackData.Clear();
|
|
/// \todo Update according to sendHeader()'s generated data.
|
|
//0x100 - 1 + currentPacket.getTrackId()
|
|
if (myMeta.tracks[currentPacket.getTrackId()].type == "video"){
|
|
PackData.PID(0x100);
|
|
}else{
|
|
PackData.PID(0x101);
|
|
}
|
|
PackData.ContinuityCounter((*ContCounter)++);
|
|
if (first){
|
|
PackData.UnitStart(1);
|
|
if (IsKeyFrame){
|
|
PackData.RandomAccess(1);
|
|
PackData.PCR(currentPacket.getTime() * 27000);
|
|
}
|
|
first = false;
|
|
}
|
|
unsigned int toSend = PackData.AddStuffing(ToPack.bytes(184));
|
|
std::string gonnaSend = ToPack.remove(toSend);
|
|
PackData.FillFree(gonnaSend);
|
|
HTTP_S.Chunkify(PackData.ToString(), 188, myConn);
|
|
PacketNumber ++;
|
|
}
|
|
}
|
|
|
|
int OutHLS::canSeekms(unsigned int ms){
|
|
//no tracks? Frame too new by definition.
|
|
if ( !myMeta.tracks.size()){
|
|
return 1;
|
|
}
|
|
//loop trough all the tracks
|
|
for (std::map<int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
|
//return "too late" if one track is past this point
|
|
if (ms < it->second.firstms){
|
|
return -1;
|
|
}
|
|
//return "too early" if one track is not yet at this point
|
|
if (ms > it->second.lastms){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void OutHLS::onRequest(){
|
|
while (HTTP_R.Read(myConn)){
|
|
DEBUG_MSG(DLVL_DEVEL, "Received request: %s", HTTP_R.getUrl().c_str());
|
|
AppleCompat = (HTTP_R.GetHeader("User-Agent").find("Apple") != std::string::npos);
|
|
initialize();
|
|
if (HTTP_R.url.find(".m3u") == std::string::npos){
|
|
std::string tmpStr = HTTP_R.getUrl();
|
|
std::string fmtStr = "/hls/" + streamName + "/%u_%u/%llu_%llu.ts";
|
|
long long unsigned int from;
|
|
if (sscanf(tmpStr.c_str(), fmtStr.c_str(), &vidTrack, &audTrack, &from, &until) != 4){
|
|
fmtStr = "/hls/" + streamName + "/%u/%llu_%llu.ts";
|
|
if (sscanf(tmpStr.c_str(), fmtStr.c_str(), &vidTrack, &from, &until) != 3){
|
|
WARN_MSG("Could not parse URL: %s", HTTP_R.getUrl().c_str());
|
|
HTTP_S.Clean();
|
|
HTTP_S.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
|
|
myConn.SendNow(HTTP_S.BuildResponse("404", "URL mismatch"));
|
|
HTTP_R.Clean(); //clean for any possible next requests
|
|
continue;
|
|
}else{
|
|
selectedTracks.clear();
|
|
selectedTracks.insert(vidTrack);
|
|
}
|
|
}else{
|
|
selectedTracks.clear();
|
|
selectedTracks.insert(vidTrack);
|
|
selectedTracks.insert(audTrack);
|
|
}
|
|
|
|
if (myMeta.live){
|
|
/// \todo Detection of out-of-range parts.
|
|
int seekable = canSeekms(from);
|
|
if (seekable < 0){
|
|
HTTP_S.Clean();
|
|
HTTP_S.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n");
|
|
myConn.SendNow(HTTP_S.BuildResponse("412", "Fragment out of range"));
|
|
HTTP_R.Clean(); //clean for any possible next requests
|
|
DEBUG_MSG(DLVL_WARN, "Fragment @ %llu too old", from);
|
|
continue;
|
|
}
|
|
if (seekable > 0){
|
|
HTTP_S.Clean();
|
|
HTTP_S.SetBody("Proxy, re-request this in a second or two.\n");
|
|
myConn.SendNow(HTTP_S.BuildResponse("208", "Ask again later"));
|
|
HTTP_R.Clean(); //clean for any possible next requests
|
|
DEBUG_MSG(DLVL_WARN, "Fragment @ %llu not available yet", from);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
seek(from);
|
|
lastVid = from * 90;
|
|
|
|
HTTP_S.Clean();
|
|
HTTP_S.SetHeader("Content-Type", "video/mp2t");
|
|
HTTP_S.StartResponse(HTTP_R, myConn);
|
|
PacketNumber = 0;
|
|
parseData = true;
|
|
wantRequest = false;
|
|
}else{
|
|
initialize();
|
|
std::string request = HTTP_R.url.substr(HTTP_R.url.find("/", 5) + 1);
|
|
HTTP_S.Clean();
|
|
if (HTTP_R.url.find(".m3u8") != std::string::npos){
|
|
HTTP_S.SetHeader("Content-Type", "audio/x-mpegurl");
|
|
}else{
|
|
HTTP_S.SetHeader("Content-Type", "audio/mpegurl");
|
|
}
|
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
|
std::string manifest;
|
|
if (request.find("/") == std::string::npos){
|
|
manifest = liveIndex();
|
|
}else{
|
|
int selectId = atoi(request.substr(0,request.find("/")).c_str());
|
|
manifest = liveIndex(selectId);
|
|
}
|
|
HTTP_S.SetBody(manifest);
|
|
HTTP_S.SendResponse("200", "OK", myConn);
|
|
}
|
|
HTTP_R.Clean(); //clean for any possible next requests
|
|
}
|
|
}
|
|
}
|