From dda3a24805d9b7b3cdfa53136b184fdc6208629c Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 17 Apr 2017 17:41:38 +0200 Subject: [PATCH] Working .wav live and VoD output, PCM A-law and MP3 --- CMakeLists.txt | 1 + lib/riff.cpp | 17 +++++ lib/riff.h | 2 + src/output/output_wav.cpp | 126 ++++++++++++++++++++++++++++++++++++++ src/output/output_wav.h | 18 ++++++ 5 files changed, 164 insertions(+) create mode 100644 src/output/output_wav.cpp create mode 100644 src/output/output_wav.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 20b5ebc1..0e6d223d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -405,6 +405,7 @@ makeOutput(HTTPTS httpts http ts) makeOutput(HLS hls http ts) makeOutput(Push push)#LTS makeOutput(RTSP rtsp)#LTS +makeOutput(WAV wav)#LTS if (NOT DEFINED NOSSL ) makeOutput(HTTPS https)#LTS target_link_libraries(MistOutHTTPS mbedtls mbedcrypto mbedx509) diff --git a/lib/riff.cpp b/lib/riff.cpp index 0781db72..1315e9bd 100644 --- a/lib/riff.cpp +++ b/lib/riff.cpp @@ -132,6 +132,18 @@ namespace RIFF{ } } } + std::string fmt::generate(uint16_t format, uint16_t channels, uint32_t hz, uint32_t bps, uint16_t blocksize, uint16_t size){ + std::string ret("fmt \022\000\000\000", 8); + ret.append(std::string((size_t)18, '\000')); + Bit::htobs_le((char*)ret.data()+8, format); + Bit::htobs_le((char*)ret.data()+10, channels); + Bit::htobl_le((char*)ret.data()+12, hz); + Bit::htobl_le((char*)ret.data()+16, bps); + Bit::htobs_le((char*)ret.data()+20, blocksize); + Bit::htobs_le((char*)ret.data()+22, size); + Bit::htobs_le((char*)ret.data()+24, 0); + return ret; + } uint32_t fact::getSamplesPerChannel() const{ if (!p){return 0;} @@ -143,6 +155,11 @@ namespace RIFF{ indent += 1; o << std::string(indent, ' ') << "Samples per channel: " << getSamplesPerChannel() << std::endl; } + std::string fact::generate(uint32_t samples){ + std::string ret("fact\004\000\000\000\000\000\000\000", 12); + Bit::htobl_le((char*)ret.data()+8, samples); + return ret; + } std::string ISFT::getSoftware() const{ if (!p){return 0;} diff --git a/lib/riff.h b/lib/riff.h index 72dc7fe5..7b038598 100644 --- a/lib/riff.h +++ b/lib/riff.h @@ -41,6 +41,7 @@ namespace RIFF{ /// WAVE "fmt " class. class fmt : public Chunk{ public: + static std::string generate(uint16_t format, uint16_t channels, uint32_t hz, uint32_t bps, uint16_t blocksize, uint16_t size); fmt(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} uint16_t getFormat() const; std::string getCodec() const; @@ -59,6 +60,7 @@ namespace RIFF{ /// WAVE fact class. class fact : public Chunk { public: + static std::string generate(uint32_t samples); fact(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} uint32_t getSamplesPerChannel() const; virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; diff --git a/src/output/output_wav.cpp b/src/output/output_wav.cpp new file mode 100644 index 00000000..ea54575e --- /dev/null +++ b/src/output/output_wav.cpp @@ -0,0 +1,126 @@ +#include "output_wav.h" +#include + +namespace Mist{ + OutWAV::OutWAV(Socket::Connection &conn) : HTTPOutput(conn){ + if (config->getString("target").size()){ + initialize(); + if (!streamName.size()){ + WARN_MSG("Recording unconnected WAV output to file! Cancelled."); + conn.close(); + return; + } + if (config->getString("target") == "-"){ + parseData = true; + wantRequest = false; + INFO_MSG("Outputting %s to stdout in WAV format", streamName.c_str()); + return; + } + if (!myMeta.tracks.size()){ + INFO_MSG("Stream not available - aborting"); + conn.close(); + return; + } + if (connectToFile(config->getString("target"))){ + parseData = true; + wantRequest = false; + INFO_MSG("Recording %s to %s in WAV format", streamName.c_str(), + config->getString("target").c_str()); + return; + } + conn.close(); + } + } + + void OutWAV::init(Util::Config *cfg){ + HTTPOutput::init(cfg); + capa["name"] = "WAV"; + capa["desc"] = "Enables HTTP protocol progressive WAV streaming"; + capa["url_rel"] = "/$.wav"; + capa["url_match"] = "/$.wav"; + capa["codecs"][0u][0u].append("ALAW"); + capa["codecs"][0u][0u].append("MP3"); + capa["methods"][0u]["handler"] = "http"; + capa["methods"][0u]["type"] = "html5/audio/wav"; + capa["methods"][0u]["priority"] = 1ll; + capa["push_urls"].append("/*.wav"); + + JSON::Value opt; + opt["arg"] = "string"; + opt["default"] = ""; + opt["arg_num"] = 1ll; + opt["help"] = "Target filename to store WAV file as, or - for stdout."; + cfg->addOption("target", opt); + } + + bool OutWAV::isRecording(){return config->getString("target").size();} + + void OutWAV::sendNext(){ + char *dataPointer = 0; + unsigned int len = 0; + thisPacket.getString("data", dataPointer, len); + myConn.SendNow(dataPointer, len); + } + + void OutWAV::sendHeader(){ + if (!isRecording()){ + H.Clean(); + H.SetHeader("Content-Type", "audio/wav"); + H.protocol = "HTTP/1.0"; + H.setCORSHeaders(); + H.SendResponse("200", "OK", myConn); + } + DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; + // Send WAV header + char riffHeader[] = "RIFF\377\377\377\377WAVE"; + // For live we send max allowed size + // VoD size of the whole thing is RIFF(4)+fmt(26)+fact(12)+LIST(30)+data(8)+data itself + uint32_t total_data = 0xFFFFFFFFul - 80; + if (!myMeta.live){ + total_data = 0; + for (std::deque::iterator it = Trk.keySizes.begin(); it != Trk.keySizes.end(); + ++it){ + total_data += *it; + } + } + Bit::htobl_le(riffHeader + 4, 80 + total_data); + myConn.SendNow(riffHeader, 12); + // Send format details + uint16_t fmt = 0; + if (Trk.codec == "ALAW"){fmt = 6;} + if (Trk.codec == "MP3"){fmt = 85;} + myConn.SendNow(RIFF::fmt::generate(fmt, Trk.channels, Trk.rate, Trk.bps, + Trk.channels * (Trk.size << 3), Trk.size)); + // Send sample count per channel + if (!myMeta.live){ + myConn.SendNow(RIFF::fact::generate(((Trk.lastms - Trk.firstms) * Trk.rate) / 1000)); + }else{ + myConn.SendNow(RIFF::fact::generate(0xFFFFFFFFul)); + } + // Send MistServer identifier + myConn.SendNow("LIST\026\000\000\000infoISFT\012\000\000\000MistServer", 30); + // Start data chunk + char dataChunk[] = "data\377\377\377\377"; + Bit::htobl_le(dataChunk + 4, total_data); + myConn.SendNow(dataChunk, 8); + sentHeader = true; + } + + void OutWAV::onHTTP(){ + std::string method = H.method; + + H.Clean(); + H.setCORSHeaders(); + if (method == "OPTIONS" || method == "HEAD"){ + H.SetHeader("Content-Type", "audio/wav"); + H.protocol = "HTTP/1.0"; + H.SendResponse("200", "OK", myConn); + H.Clean(); + return; + } + + parseData = true; + wantRequest = false; + } +} + diff --git a/src/output/output_wav.h b/src/output/output_wav.h new file mode 100644 index 00000000..bc164d20 --- /dev/null +++ b/src/output/output_wav.h @@ -0,0 +1,18 @@ +#include "output_http.h" + + +namespace Mist { + class OutWAV : public HTTPOutput { + public: + OutWAV(Socket::Connection & conn); + static void init(Util::Config * cfg); + void onHTTP(); + void sendNext(); + void sendHeader(); + private: + bool isRecording(); + }; +} + +typedef Mist::OutWAV mistOut; +