123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- #include <string>
- #include <regex>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <sys/stat.h>
- #include <unistd.h>
- #include "utils.hh"
- #include "mpegts.hh"
- #include "sfinputstream.hh"
- #include "sfoutputstream.hh"
- #include "wmcommon.hh"
- #include "wavdata.hh"
- #include "config.h"
- using std::string;
- using std::vector;
- using std::regex;
- using std::map;
- using std::min;
- #if !HAVE_FFMPEG
- int
- hls_prepare (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master)
- {
- error ("audiowmark: hls support is not available in this build of audiowmark\n");
- return 1;
- }
- int
- hls_add (const Key& key, const string& infile, const string& outfile, const string& bits)
- {
- error ("audiowmark: hls support is not available in this build of audiowmark\n");
- return 1;
- }
- #else
- #include "hlsoutputstream.hh"
- static bool
- file_exists (const string& filename)
- {
- struct stat st;
- if (stat (filename.c_str(), &st) == 0)
- {
- return S_ISREG (st.st_mode);
- }
- return false;
- }
- static string
- args2string (const vector<string>& args)
- {
- string result;
- bool first = true;
- for (auto a : args)
- {
- if (!first)
- result += " ";
- first = false;
- result += a;
- }
- return result;
- }
- static Error
- run (const vector<string>& args, vector<string> *pipe_out = nullptr)
- {
- auto report_error = [=] { error ("audiowmark: failed to execute %s\n", args2string (args).c_str()); };
- char *argv[args.size() + 1];
- for (size_t i = 0; i < args.size(); i++)
- argv[i] = (char *) args[i].c_str();
- argv[args.size()] = nullptr;
- int pipe_fds[2];
- if (pipe_out)
- {
- if (pipe (pipe_fds) == -1)
- {
- report_error();
- return Error ("pipe() failed");
- }
- }
- pid_t pid = fork();
- if (pid < 0)
- {
- if (pipe_out)
- {
- close (pipe_fds[0]);
- close (pipe_fds[1]);
- }
- report_error();
- return Error ("fork() failed");
- }
- if (pid == 0)
- {
- if (pipe_out)
- {
-
- if (dup2 (pipe_fds[1], STDOUT_FILENO) == -1)
- {
- perror ("audiowmark: dup2() failed");
- exit (127);
- }
-
- close (pipe_fds[0]);
- close (pipe_fds[1]);
- }
- execvp (argv[0], argv);
- perror ("audiowmark: execvp() failed");
-
- exit (127);
- }
-
- if (pipe_out)
- {
- close (pipe_fds[1]);
- FILE *f = fdopen (pipe_fds[0], "r");
- if (!f)
- {
- close (pipe_fds[0]);
- report_error();
- return Error ("fdopen() pipe failed");
- }
- char buffer[1024];
- while (fgets (buffer, 1024, f))
- {
- if (strlen (buffer) && buffer[strlen (buffer) - 1] == '\n')
- buffer[strlen (buffer) - 1] = 0;
- if (pipe_out)
- pipe_out->push_back (buffer);
- }
- fclose (f);
- }
- int status;
- pid_t exited = waitpid (pid, &status, 0);
- if (exited < 0)
- {
- report_error();
- return Error ("waitpid() failed");
- }
- if (WIFEXITED (status))
- {
- int exit_status = WEXITSTATUS (status);
- if (exit_status != 0)
- {
- report_error();
- return Error (string_printf ("subprocess failed / exit status %d", exit_status));
- }
- }
- else
- {
- report_error();
- return Error ("child didn't exit normally");
- }
- return Error::Code::NONE;
- }
- Error
- ff_decode (const string& filename, WavData& out_wav_data)
- {
- FILE *tmp_file = tmpfile();
- ScopedFile tmp_file_s (tmp_file);
- string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file));
- if (!tmp_file)
- return Error ("failed to create temp file");
- Error err = run ({"ffmpeg", "-v", "error", "-y", "-f", "mpegts", "-i", filename, "-f", "wav", tmp_file_name});
- if (err)
- return err;
- err = out_wav_data.load (tmp_file_name);
- return err;
- }
- int
- hls_add (const Key& key, const string& infile, const string& outfile, const string& bits)
- {
- TSReader reader;
- Error err = reader.load (infile);
- if (err)
- {
- error ("hls: %s\n", err.message());
- return 1;
- }
- const TSReader::Entry *full_flac = reader.find ("full.flac");
- if (!full_flac)
- {
- error ("hls: no embedded context found in %s\n", infile.c_str());
- return 1;
- }
- SFInputStream in_stream;
- err = in_stream.open (&full_flac->data);
- if (err)
- {
- error ("hls: %s\n", err.message());
- return 1;
- }
- map<string, string> vars = reader.parse_vars ("vars");
- bool missing_vars = false;
- auto get_var = [&] (const std::string& var) {
- auto it = vars.find (var);
- if (it == vars.end())
- {
- error ("audiowmark: hls segment is missing value for required variable '%s'\n", var.c_str());
- missing_vars = true;
- return "";
- }
- else
- return it->second.c_str();
- };
- size_t start_pos = atoi (get_var ("start_pos"));
- size_t prev_size = atoi (get_var ("prev_size"));
- size_t size = atoi (get_var ("size"));
- double pts_start = atof (get_var ("pts_start"));
- int bit_rate = atoi (get_var ("bit_rate"));
- size_t prev_ctx = min<size_t> (1024 * 3, prev_size);
- string channel_layout = get_var ("channel_layout");
- if (missing_vars)
- return 1;
- if (Params::hls_bit_rate)
- bit_rate = Params::hls_bit_rate;
- HLSOutputStream out_stream (in_stream.n_channels(), in_stream.sample_rate(), in_stream.bit_depth());
- out_stream.set_bit_rate (bit_rate);
- out_stream.set_channel_layout (channel_layout);
-
- const size_t shift = 0;
- const size_t cut_aac_frames = (prev_ctx + shift) / 1024;
- const size_t delete_input_start = prev_size - prev_ctx;
- const size_t keep_aac_frames = size / 1024;
- err = out_stream.open (outfile, cut_aac_frames, keep_aac_frames, pts_start, delete_input_start);
- if (err)
- {
- error ("audiowmark: error opening HLS output stream %s: %s\n", outfile.c_str(), err.message());
- return 1;
- }
- int wm_rc = add_stream_watermark (key, &in_stream, &out_stream, bits, start_pos - prev_size);
- if (wm_rc != 0)
- return wm_rc;
- info ("AAC Bitrate: %d\n", bit_rate);
- return 0;
- }
- Error
- bit_rate_from_m3u8 (const string& m3u8, const WavData& wav_data, int& bit_rate)
- {
- FILE *tmp_file = tmpfile();
- ScopedFile tmp_file_s (tmp_file);
- string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file));
- if (!tmp_file)
- return Error ("failed to create temp file");
- Error err = run ({"ffmpeg", "-v", "error", "-y", "-i", m3u8, "-c:a", "copy", "-f", "adts", tmp_file_name});
- if (err)
- return err;
- struct stat stat_buf;
- if (stat (tmp_file_name.c_str(), &stat_buf) != 0)
- {
- return Error (string_printf ("failed to stat temporary aac file: %s", strerror (errno)));
- }
- double seconds = double (wav_data.n_frames()) / wav_data.sample_rate();
- bit_rate = stat_buf.st_size / seconds * 8;
- return Error::Code::NONE;
- }
- Error
- load_audio_master (const string& filename, WavData& audio_master_data)
- {
- FILE *tmp_file = tmpfile();
- ScopedFile tmp_file_s (tmp_file);
- string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file));
- if (!tmp_file)
- return Error ("failed to create temp file");
-
- Error err = run ({"ffmpeg", "-v", "error", "-y", "-i", filename, "-f", "wav", tmp_file_name});
- if (err)
- return err;
- err = audio_master_data.load (tmp_file_name);
- if (err)
- return err;
- return Error::Code::NONE;
- }
- Error
- probe_input_segment (const string& filename, map<string, string>& params)
- {
- TSReader reader;
- Error err = reader.load (filename);
- if (err)
- {
- error ("audiowmark: hls: failed to read mpegts input file: %s\n", filename.c_str());
- return err;
- }
- if (reader.entries().size())
- {
- error ("audiowmark: hls: file appears to be already prepared: %s\n", filename.c_str());
- return Error ("input for hls-prepare must not contain context");
- }
- vector<string> format_out;
- err = run ({"ffprobe", "-v", "error", "-print_format", "compact", "-show_streams", filename}, &format_out);
- if (err)
- {
- error ("audiowmark: hls: failed to validate input file: %s\n", filename.c_str());
- return err;
- }
- for (auto o : format_out)
- {
-
- string key, value;
- bool in_key = true;
- for (char c : '|' + o + '|')
- {
- if (c == '=')
- {
- in_key = false;
- }
- else if (c == '|')
- {
- params[key] = value;
- in_key = true;
- key = "";
- value = "";
- }
- else
- {
- if (in_key)
- key += c;
- else
- value += c;
- }
- }
- }
- return Error::Code::NONE;
- }
- int
- hls_prepare (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master)
- {
- string in_name = in_dir + "/" + filename;
- FILE *in_file = fopen (in_name.c_str(), "r");
- ScopedFile in_file_s (in_file);
- if (!in_file)
- {
- error ("audiowmark: error opening input playlist %s\n", in_name.c_str());
- return 1;
- }
- int mkret = mkdir (out_dir.c_str(), 0755);
- if (mkret == -1 && errno != EEXIST)
- {
- error ("audiowmark: unable to create directory %s: %s\n", out_dir.c_str(), strerror (errno));
- return 1;
- }
- string out_name = out_dir + "/" + filename;
- if (file_exists (out_name))
- {
- error ("audiowmark: output file already exists: %s\n", out_name.c_str());
- return 1;
- }
- FILE *out_file = fopen (out_name.c_str(), "w");
- ScopedFile out_file_s (out_file);
- if (!out_file)
- {
- error ("audiowmark: error opening output playlist %s\n", out_name.c_str());
- return 1;
- }
- WavData audio_master_data;
- Error err = load_audio_master (audio_master, audio_master_data);
- if (err)
- {
- error ("audiowmark: failed to load audio master: %s\n", audio_master.c_str());
- return 1;
- }
- struct Segment
- {
- string name;
- size_t size;
- map<string, string> vars;
- };
- vector<Segment> segments;
- char buffer[1024];
- const regex blank_re (R"(\s*(#.*)?)");
- while (fgets (buffer, 1024, in_file))
- {
-
- int last = strlen (buffer) - 1;
- while (last > 0 && (buffer[last] == '\n' || buffer[last] == '\r'))
- buffer[last--] = 0;
- string s = buffer;
- std::smatch match;
- if (regex_match (s, blank_re))
- {
-
- fprintf (out_file, "%s\n", s.c_str());
- }
- else
- {
- fprintf (out_file, "%s\n", s.c_str());
- Segment segment;
- segment.name = s;
- segments.push_back (segment);
- }
- }
- for (auto& segment : segments)
- {
- map<string, string> params;
- string segname = in_dir + "/" + segment.name;
- Error err = probe_input_segment (segname, params);
- if (err)
- {
- error ("audiowmark: hls: %s\n", err.message());
- return 1;
- }
-
- if (atoi (params["index"].c_str()) != 0)
- {
- error ("audiowmark: hls segment '%s' contains more than one stream\n", segname.c_str());
- return 1;
- }
- if (params["codec_name"] != "aac")
- {
- error ("audiowmark: hls segment '%s' is not encoded using AAC\n", segname.c_str());
- return 1;
- }
- int segment_channels = atoi (params["channels"].c_str());
- if (segment_channels != audio_master_data.n_channels())
- {
- error ("audiowmark: number of channels mismatch:\n - hls segment '%s' has %d channels\n - audio master '%s' has %d channels\n",
- segname.c_str(), segment_channels, audio_master.c_str(), audio_master_data.n_channels());
- return 1;
- }
-
- if (params["channel_layout"].empty())
- {
- error ("audiowmark: hls segment '%s' has no channel_layout entry\n", segname.c_str());
- return 1;
- }
- segment.vars["channel_layout"] = params["channel_layout"];
-
- if (params["start_time"].empty())
- {
- error ("audiowmark: hls segment '%s' has no start_time entry\n", segname.c_str());
- return 1;
- }
- segment.vars["pts_start"] = params["start_time"];
- }
-
- int bit_rate = 0;
- if (!Params::hls_bit_rate)
- {
- err = bit_rate_from_m3u8 (in_dir + "/" + filename, audio_master_data, bit_rate);
- if (err)
- {
- error ("audiowmark: bit-rate detection failed: %s\n", err.message());
- return 1;
- }
- info ("AAC Bitrate: %d (detected)\n", bit_rate);
- }
- else
- {
- bit_rate = Params::hls_bit_rate;
- info ("AAC Bitrate: %d\n", bit_rate);
- }
- info ("Segments: %zd\n", segments.size());
- size_t start_pos = 0;
- for (auto& segment : segments)
- {
- WavData out;
- Error err = ff_decode (in_dir + "/" + segment.name, out);
- if (err)
- {
- error ("audiowmark: hls: ff_decode failed: %s\n", err.message());
- return 1;
- }
- segment.size = out.n_values() / out.n_channels();
- if ((segment.size % 1024) != 0)
- {
- error ("audiowmark: hls input segments need 1024-sample alignment (due to AAC)\n");
- return 1;
- }
-
- const size_t ctx_3sec = 3 * out.sample_rate();
- const size_t prev_size = min<size_t> (start_pos, ctx_3sec);
- const size_t segment_size_with_ctx = prev_size + segment.size + ctx_3sec;
- segment.vars["start_pos"] = string_printf ("%zd", start_pos);
- segment.vars["size"] = string_printf ("%zd", segment.size);
- segment.vars["prev_size"] = string_printf ("%zd", prev_size);
- segment.vars["bit_rate"] = string_printf ("%d", bit_rate);
-
- const size_t start_point = min (start_pos - prev_size, audio_master_data.n_frames());
- const size_t end_point = min (start_point + segment_size_with_ctx, audio_master_data.n_frames());
- vector<float> out_signal (audio_master_data.samples().begin() + start_point * audio_master_data.n_channels(),
- audio_master_data.samples().begin() + end_point * audio_master_data.n_channels());
-
- out_signal.resize (segment_size_with_ctx * audio_master_data.n_channels());
- vector<unsigned char> full_flac_mem;
- SFOutputStream out_stream;
- err = out_stream.open (&full_flac_mem,
- audio_master_data.n_channels(), audio_master_data.sample_rate(), audio_master_data.bit_depth(),
- SFOutputStream::OutFormat::FLAC);
- if (err)
- {
- error ("audiowmark: hls: open context flac failed: %s\n", err.message());
- return 1;
- }
- err = out_stream.write_frames (out_signal);
- if (err)
- {
- error ("audiowmark: hls: write context flac failed: %s\n", err.message());
- return 1;
- }
- err = out_stream.close();
- if (err)
- {
- error ("audiowmark: hls: close context flac failed: %s\n", err.message());
- return 1;
- }
-
- TSWriter writer;
- writer.append_data ("full.flac", full_flac_mem);
- writer.append_vars ("vars", segment.vars);
- string out_segment = out_dir + "/" + segment.name;
- if (file_exists (out_segment))
- {
- error ("audiowmark: output file already exists: %s\n", out_segment.c_str());
- return 1;
- }
- err = writer.process (in_dir + "/" + segment.name, out_segment);
- if (err)
- {
- error ("audiowmark: processing hls segment %s failed: %s\n", segment.name.c_str(), err.message());
- return 1;
- }
-
- start_pos += segment.size;
- }
- int orig_seconds = start_pos / audio_master_data.sample_rate();
- info ("Time: %d:%02d\n", orig_seconds / 60, orig_seconds % 60);
- return 0;
- }
- #endif
|