Working MP4 intro

This commit is contained in:
Erik Zandvliet 2012-10-23 14:46:40 +02:00 committed by Thulinma
parent 07fad893ab
commit df6ea8eb0d
3 changed files with 127 additions and 173 deletions

View file

@ -8,7 +8,7 @@ EXTRA_DIST=server.html server.html.h embed.js.h
AM_CPPFLAGS = $(global_CFLAGS) $(MIST_CFLAGS)
LDADD = $(MIST_LIBS)
SUBDIRS=converters analysers
bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistPlayer
bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistConnHTTPSmooth MistPlayer
MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp tinythread.cpp tinythread.h ../VERSION
MistBuffer_LDADD=$(MIST_LIBS) -lpthread
MistController_SOURCES=controller.cpp ../VERSION ./server.html.h
@ -18,6 +18,7 @@ MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embe
MistConnHTTP_LDADD=$(MIST_LIBS) -lpthread
MistConnHTTPProgressive_SOURCES=conn_http_progressive.cpp ../VERSION
MistConnHTTPDynamic_SOURCES=conn_http_dynamic.cpp ../VERSION
MistConnHTTPSmooth_SOURCES=conn_http_smooth.cpp ../VERSION
MistPlayer_SOURCES=player.cpp
MistPlayer_LDADD=$(MIST_LIBS)

View file

@ -318,6 +318,12 @@ namespace Connector_HTTP{
H.SetVar("stream", streamname);
return "dynamic";
}
if (url.find("/smooth/") != std::string::npos ) {
std::string streamname = url.substr(8,url.find("/",8)-8);
Util::Stream::sanitizeName(streamname);
H.SetVar("stream", streamname);
return "smooth";
}
if (url.length() > 4){
std::string ext = url.substr(url.length() - 4, 4);
if (ext == ".flv" || ext == ".mp3"){
@ -404,6 +410,7 @@ int main(int argc, char ** argv){
//start progressive and dynamic handlers from the same folder as this application
Util::Procs::Start("progressive", Util::getMyPath() + "MistConnHTTPProgressive -n");
Util::Procs::Start("dynamic", Util::getMyPath() + "MistConnHTTPDynamic -n");
Util::Procs::Start("smooth", Util::getMyPath() + "MistConnHTTPSmooth -n");
while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept();

View file

@ -25,136 +25,55 @@
/// Holds everything unique to HTTP Dynamic Connector.
namespace Connector_HTTP{
std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){
std::string empty;
MP4::ASRT asrt;
if (starttime == 0){
asrt.setUpdate(false);
}else{
asrt.setUpdate(true);
}
asrt.setVersion(1);
asrt.setQualityEntry(empty, 0);
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
asrt.setSegmentRun(1, 20000, 0);
}else{
asrt.setSegmentRun(1, metadata["keytime"].size(), 0);
}
MP4::AFRT afrt;
if (starttime == 0){
afrt.setUpdate(false);
}else{
afrt.setUpdate(true);
}
afrt.setVersion(1);
afrt.setTimeScale(1000);
afrt.setQualityEntry(empty, 0);
MP4::afrt_runtable afrtrun;
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
afrtrun.firstFragment = 1;
afrtrun.firstTimestamp = 0;
if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){
afrtrun.duration = 2000;
}else{
afrtrun.duration = metadata["video"]["keyms"].asInt();
}
afrt.setFragmentRun(afrtrun, 0);
}else{
for (int i = 0; i < metadata["keytime"].size(); i++){
afrtrun.firstFragment = i+1;
afrtrun.firstTimestamp = metadata["keytime"][i].asInt();
if (i+1 < metadata["keytime"].size()){
afrtrun.duration = metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt();
}else{
if (metadata["lastms"].asInt()){
afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt();
}else{
afrtrun.duration = 3000;//guess 3 seconds if unknown
}
}
afrt.setFragmentRun(afrtrun, i);
}
}
MP4::ABST abst;
abst.setVersion(1);
abst.setBootstrapinfoVersion(1);
abst.setProfile(0);
if (starttime == 0){
abst.setUpdate(false);
}else{
abst.setUpdate(true);
}
abst.setTimeScale(1000);
if (metadata.isMember("length") && metadata["length"].asInt() > 0){
abst.setLive(false);
if (metadata["lastms"].asInt()){
abst.setCurrentMediaTime(metadata["lastms"].asInt());
}else{
abst.setCurrentMediaTime(1000*metadata["length"].asInt());
}
}else{
abst.setLive(true);
abst.setCurrentMediaTime(0xFFFFFFFF);
}
abst.setSmpteTimeCodeOffset(0);
abst.setMovieIdentifier(MovieId);
abst.setServerEntry(empty, 0);
abst.setQualityEntry(empty, 0);
abst.setDrmData(empty);
abst.setMetaData(empty);
abst.setSegmentRunTable(asrt, 0);
abst.setFragmentRunTable(afrt, 0);
#if DEBUG >= 8
std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl;
#endif
return std::string((char*)abst.asBox(), (int)abst.boxedSize());
}
/// Returns a F4M-format manifest file
/// Returns a Smooth-format manifest file
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
std::string Result;
if (metadata.isMember("length") && metadata["length"].asInt() > 0){
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
"<id>" + MovieId + "</id>\n"
"<width>" + metadata["video"]["width"].asString() + "</width>\n"
"<height>" + metadata["video"]["height"].asString() + "</height>\n"
"<duration>" + metadata["length"].asString() + ".000</duration>\n"
"<mimeType>video/mp4</mimeType>\n"
"<streamType>recorded</streamType>\n"
"<deliveryType>streaming</deliveryType>\n"
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n"
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\">\n"
"<metadata>AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=</metadata>\n"
"</media>\n"
"</manifest>\n";
}else{
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
"<id>" + MovieId + "</id>\n"
"<mimeType>video/mp4</mimeType>\n"
"<streamType>live</streamType>\n"
"<deliveryType>streaming</deliveryType>\n"
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n"
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\"></media>\n"
"</manifest>\n";
std::stringstream Result;
Result << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
Result << "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"1000\" Duration=\"" << metadata["lastms"].asInt() << "\">\n";
if( metadata.isMember( "audio" ) ) {
Result << " <StreamIndex Type=\"audio\" QualityLevels=\"1\" TimeScale=\"1000\" Name=\"audio\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/A({start time})\">\n";
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["audio"]["bps"].asInt()*8 << "\" CodecPrivateData=\"";
Result << std::hex;
for( int i = 0; i < metadata["audio"]["init"].asString().size(); i++ ) {
Result << (int)metadata["audio"]["init"].asString()[i];
}
Result << std::dec;
Result << "\" SamplingRate=\"" << metadata["audio"]["rate"].asInt() << "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" FourCC=\"AACL\" />\n";
for( int i = 0; i < metadata["keytime"].size(); i++ ) {
Result << " <c ";
if( i == 0 ) { Result << "t=\"0\" "; }
Result << "d=\"" << metadata["keytime"][i].asInt() << "\"/>\n";
}
Result << " </StreamIndex>\n";
}
if( metadata.isMember( "video" ) ) {
Result << " <StreamIndex Type=\"video\" QualityLevels=\"1\" TimeScale=\"1000\" Name=\"video\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/V({start time})\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" DisplayWidth=\"" << metadata["video"]["width"].asInt() << "\" DisplayHeight=\"" << metadata["video"]["height"].asInt() << "\">\n";
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["video"]["bps"].asInt()*8 << "\" CodecPrivateData=\"";
Result << std::hex;
for( int i = 0; i < metadata["video"]["init"].asString().size(); i++ ) {
Result << (int)metadata["video"]["init"].asString()[i];
}
Result << std::dec;
Result << "\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" FourCC=\"AVC1\"/>\n";
for( int i = 0; i < metadata["keytime"].size(); i++ ) {
Result << " <c ";
if( i == 0 ) { Result << "t=\"0\" "; }
Result << "d=\"" << metadata["keytime"][i].asInt() << "\"/>\n";
}
Result << " </StreamIndex>\n";
}
Result << "</SmoothStreamingMedia>\n";
#if DEBUG >= 8
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
#endif
return Result;
return Result.str();
}//BuildManifest
/// Main function for Connector_HTTP_Dynamic
int Connector_HTTP_Dynamic(Socket::Connection conn){
std::deque<std::string> FlashBuf;
std::vector<int> Timestamps;
int FlashBufSize = 0;
long long int FlashBufTime = 0;
FLV::Tag tmp;//temporary tag
@ -170,10 +89,14 @@ namespace Connector_HTTP{
std::string streamname;
std::string recBuffer = "";
bool wantsVideo = false;
bool wantsAudio = false;
std::string Quality;
int Segment = -1;
int ReqFragment = -1;
int temp;
std::string tempStr;
int Flash_RequestPending = 0;
unsigned int lastStats = 0;
conn.setBlocking(false);//do not block on conn.spool() when no data is available
@ -195,8 +118,8 @@ namespace Connector_HTTP{
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
#endif
conn.setHost(HTTP_R.GetHeader("X-Origin"));
if (HTTP_R.url.find("f4m") == std::string::npos){
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
if (HTTP_R.url.find("Manifest") == std::string::npos){
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-8);
if (!ss){
ss = Util::Stream::getStream(streamname);
if (!ss.connected()){
@ -205,7 +128,7 @@ namespace Connector_HTTP{
#endif
ss.close();
HTTP_S.Clean();
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
conn.SendNow(HTTP_S.BuildResponse("404", "Not found"));
ready4data = false;
continue;
@ -213,21 +136,22 @@ namespace Connector_HTTP{
ss.setBlocking(false);
inited = true;
}
Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 );
Quality = Quality.substr(0, Quality.find("Seg"));
temp = HTTP_R.url.find("Seg") + 3;
Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str());
temp = HTTP_R.url.find("Frag") + 4;
ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() );
Quality = HTTP_R.url.substr( HTTP_R.url.find("/Q(",8)+3 );
Quality = Quality.substr(0, Quality.find(")"));
tempStr = HTTP_R.url.substr( HTTP_R.url.find(")/") + 2 );
if( tempStr[0] == 'A' ) { wantsAudio = true; }
if( tempStr[0] == 'V' ) { wantsVideo = true; }
tempStr = tempStr.find("(") + 1;
ReqFragment = atoi( tempStr.substr(0,tempStr.find(")")).c_str() );
#if DEBUG >= 4
printf( "Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
printf( "Quality: %s, Frag %d\n", Quality.c_str(), ReqFragment);
#endif
std::stringstream sstream;
sstream << "f " << ReqFragment << "\no \n";
sstream << "s " << ReqFragment << "\no \n";
ss.SendNow(sstream.str().c_str());
Flash_RequestPending++;
}else{
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-8);
if (!Strm.metadata.isNull()){
HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml");
@ -264,7 +188,7 @@ namespace Connector_HTTP{
#endif
ss.close();
HTTP_S.Clean();
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
conn.SendNow(HTTP_S.BuildResponse("404", "Not found"));
ready4data = false;
continue;
@ -278,7 +202,7 @@ namespace Connector_HTTP{
unsigned int now = Util::epoch();
if (now != lastStats){
lastStats = now;
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
}
if (ss.spool()){
while (Strm.parsePacket(ss.Received())){
@ -306,6 +230,9 @@ namespace Connector_HTTP{
pending_manifest = false;
}
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;}
if ( Strm.lastType() == DTSC::PAUSEMARK ) {
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
}
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
#if DEBUG >= 4
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
@ -319,21 +246,51 @@ namespace Connector_HTTP{
HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "video/mp4");
HTTP_S.SetBody("");
HTTP_S.SetHeader("Content-Length", FlashBufSize+8);//32+33+btstrp.size());
MP4::MFHD mfhd_box;
mfhd_box.setSequenceNumber( 1 );
MP4::TFHD tfhd_box;
tfhd_box.setFlags( MP4::tfhdSampleFlag );
tfhd_box.setTrackID( 1 );
tfhd_box.setDefaultSampleFlags( MP4::noIPicture | MP4::noDisposable | MP4::noKeySample );
MP4::TRUN trun_box;
//maybe reinsert dataOffset
trun_box.setFlags( MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize );
trun_box.setFirstSampleFlags( MP4::isIPicture | MP4::noDisposable | MP4::isKeySample );
std::deque< std::string >::iterator FlashBufIter = FlashBuf.begin();
for( int i = 0; i < FlashBuf.size(); i++ ) {
MP4::trunSampleInformation trunSample;
trunSample.sampleSize = (*FlashBufIter).size();
trunSample.sampleDuration = Timestamps[i+1]-Timestamps[i];
trun_box.setSampleInformation( trunSample, i );
FlashBufIter ++;
}
MP4::Box sdtp_box;
sdtp_box.setType( "sdtp" );
sdtp_box.setInt32( 0, 0 );
sdtp_box.setInt8( 0x24, 4 );
for( int i = 1; i < FlashBuf.size(); i++ ) {
sdtp_box.setInt8( 0x14, i+4 );
}
MP4::TRAF traf_box;
traf_box.setContent( tfhd_box, 0 );
traf_box.setContent( trun_box, 1 );
traf_box.setContent( sdtp_box, 2 );
MP4::MOOF moof_box;
moof_box.setContent( mfhd_box, 0 );
moof_box.setContent( traf_box, 1 );
HTTP_S.SetHeader("Content-Length", FlashBufSize+8+moof_box.boxedSize());//32+33+btstrp.size());
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
//conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21);
//unsigned long tmptime = htonl(FlashBufTime << 32);
//conn.SendNow((char*)&tmptime, 4);
//tmptime = htonl(FlashBufTime & 0xFFFFFFFF);
//conn.SendNow((char*)&tmptime, 4);
//tmptime = htonl(65);
//conn.SendNow((char*)&tmptime, 4);
//conn.SendNow(btstrp);
//conn.SendNow("\x00\x00\x00\x18moof\x00\x00\x00\x10mfhd\x00\x00\x00\x00", 20);
//unsigned long fragno = htonl(ReqFragment);
//conn.SendNow((char*)&fragno, 4);
conn.SendNow( moof_box.asBox(), moof_box.boxedSize() );
unsigned long size = htonl(FlashBufSize+8);
conn.SendNow((char*)&size, 4);
conn.SendNow("mdat", 4);
@ -349,26 +306,15 @@ namespace Connector_HTTP{
FlashBuf.clear();
FlashBufSize = 0;
}
if (Strm.lastType() == DTSC::VIDEO || Strm.lastType() == DTSC::AUDIO){
if (FlashBufSize == 0){
//fill buffer with init data, if needed.
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
tmp.DTSCAudioInit(Strm);
tmp.tagTime(Strm.getPacket(0)["time"].asInt());
FlashBuf.push_back(std::string(tmp.data, tmp.len));
FlashBufSize += tmp.len;
}
if (Strm.metadata.isMember("video") && Strm.metadata["video"].isMember("init")){
tmp.DTSCVideoInit(Strm);
tmp.tagTime(Strm.getPacket(0)["time"].asInt());
FlashBuf.push_back(std::string(tmp.data, tmp.len));
FlashBufSize += tmp.len;
}
FlashBufTime = Strm.getPacket(0)["time"].asInt();
}
tmp.DTSCLoader(Strm);
FlashBuf.push_back(std::string(tmp.data, tmp.len));
FlashBufSize += tmp.len;
if ( wantsVideo && Strm.lastType() == DTSC::VIDEO ) {
FlashBuf.push_back( Strm.lastData() );
FlashBufSize += Strm.lastData().size();
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
}
if ( wantsAudio && Strm.lastType() == DTSC::AUDIO ) {
FlashBuf.push_back( Strm.lastData() );
FlashBufSize += Strm.lastData().size();
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
}
}
if (pending_manifest && !Strm.metadata.isNull()){
@ -389,7 +335,7 @@ namespace Connector_HTTP{
}
}
conn.close();
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
ss.close();
#if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
@ -405,15 +351,15 @@ namespace Connector_HTTP{
}
#endif
return 0;
}//Connector_HTTP_Dynamic main function
}//Connector_HTTP_Smooth main function
};//Connector_HTTP_Dynamic namespace
};//Connector_HTTP_Smooth namespace
int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic");
Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth");
if (!server_socket.connected()){return 1;}
conf.activate();