Skip to content

Commit

Permalink
Allow Specifying encoding bitrate, encoding options, and autochoose t…
Browse files Browse the repository at this point in the history
…he bitrate

Allow disabling the audio
Add master playlist to tell the browser the codecs
  • Loading branch information
edman007 committed Jun 6, 2021
1 parent 30927ae commit 66513f6
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 68 deletions.
9 changes: 7 additions & 2 deletions camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ void Camera::run(void){
LERROR("Camera " + std::to_string(id) + " transcoding audio stream failed!");
return;
}
} else {
} else if(cfg.get_value("encode-format-audio") != "none") {
if (out.add_stream(stream.get_audio_stream())){
LINFO("Camera " + std::to_string(id) + " copying audio stream");
} else {
Expand Down Expand Up @@ -312,13 +312,18 @@ bool Camera::get_vencode(void){
}

bool Camera::get_aencode(void){
const std::string &user_opt = cfg.get_value("encode-format-audio");
if (user_opt == "none"){
LINFO("encode-format-audio is 'none', skipping audio");
return false;
}

AVStream *audio_stream = stream.get_audio_stream();
if (audio_stream == NULL){
LINFO("Camera " + std::to_string(id) + " has no usable audio");
return false;
}

const std::string &user_opt = cfg.get_value("encode-format-audio");
if (user_opt == "aac" || user_opt == "ac3"){
return true;//these force encoding
} else {
Expand Down
4 changes: 3 additions & 1 deletion setting.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ const std::vector<Setting> setting_options {
{"socket-path", DEFAULT_SOCKET_PATH, "Control Socket Path", "The path for the control socket, required to manage the system from the web interface", SETTING_OPTIONAL_SYSTEM},
{"broken-time-offset", "3600", "Broken Segment Future Offset", "Number of seconds in the future that will cause segments to be deleted", SETTING_OPTIONAL_SYSTEM},
{"encode-format-video", "copy", "Video Encoding codec <copy|h264|hevc>", "The codec to save video with, copy will use the camera compressed video if compatible", SETTING_OPTIONAL_CAMERA},
{"encode-format-audio", "copy", "Audio Encoding codec <copy|aac|ac3>", "The codec to save video with, copy will use the camera compressed video if compatible", SETTING_OPTIONAL_CAMERA},
{"encode-format-audio", "copy", "Audio Encoding codec <copy|none|aac|ac3>", "The codec to save video with, copy will use the camera compressed video if compatible", SETTING_OPTIONAL_CAMERA},
{"ffmpeg-encode-audio-opt", "", "FFMPEG audio encode options", "Options for the ffmpeg encoder - audio", SETTING_OPTIONAL_CAMERA},
{"ffmpeg-encode-video-opt", "", "FFMPEG video encode options", "Options for the ffmpeg encoder - video", SETTING_OPTIONAL_CAMERA},
{"ffmpeg-decode-audio-opt", "", "FFMPEG audio decode options", "Options for the ffmpeg decoder - audio", SETTING_OPTIONAL_CAMERA},
{"ffmpeg-decode-video-opt", "", "FFMPEG video decode options", "Options for the ffmpeg decoder - video", SETTING_OPTIONAL_CAMERA},
{"audio-bitrate", "auto", "audio bitrate", "Bitrate, in kbps, to encode the audio at (or 'auto' to autoselect)", SETTING_OPTIONAL_CAMERA},
{"video-bitrate", "auto", "video bitrate", "Bitrate, in kbps, to encode the video at (or 'auto' to autoselect)", SETTING_OPTIONAL_CAMERA},
{"output-extension", ".mp4", "Output Extension <.ts|.mp4>", "HLS Output Format, .ts for mpeg-ts files, .mp4 for fragmented mp4", SETTING_OPTIONAL_CAMERA},
{"video-encode-method", "auto", "Video Encode method <auto|sw|vaapi>", "Method to use for encoding, vaapi is the supported HW encoder,"
" auto tries both HW decoders before defaulting to SW, SW is a fallback", SETTING_OPTIONAL_CAMERA},
Expand Down
42 changes: 40 additions & 2 deletions stream_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ bool StreamWriter::write(PacketInterleavingBuf *pkt_buf){
if (keyframe_cbk && pkt_buf->video_keyframe){
keyframe_cbk(*(pkt_buf->in), *this);
}
LWARN("Writing: " + std::to_string(pkt_buf->dts0) + "(" + std::to_string(pkt_buf->out->dts) + ")");
//LWARN("Writing: " + std::to_string(pkt_buf->dts0) + "(" + std::to_string(pkt_buf->out->dts) + ")");
int ret = av_interleaved_write_frame(output_format_context, pkt_buf->out);
if (ret < 0) {
LERROR("Error muxing packet for camera " + cfg.get_value("camera-id"));
Expand Down Expand Up @@ -321,6 +321,7 @@ bool StreamWriter::add_encoded_stream(const AVStream *in_stream, const AVCodecCo
}

AVCodec *encoder = NULL;
AVDictionary *opts = NULL;//watch out about return and free
//Audio
if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO){
if (cfg.get_value("encode-format-audio") == "ac3"){
Expand All @@ -340,6 +341,12 @@ bool StreamWriter::add_encoded_stream(const AVStream *in_stream, const AVCodecCo
encode_ctx[out_stream->index]->channels = av_get_channel_layout_nb_channels(encode_ctx[out_stream->index]->channel_layout);
encode_ctx[out_stream->index]->sample_fmt = encoder->sample_fmts[0];
encode_ctx[out_stream->index]->time_base = (AVRational){1, encode_ctx[out_stream->index]->sample_rate};

//apply encode audio options
AVDictionary *aopts = Util::get_dict_options(cfg.get_value("ffmpeg-encode-audio-opt"));
av_dict_copy(&opts, aopts, NULL);
av_dict_free(&aopts);

} else {
LWARN("Could not find audio encoder");
return false;
Expand Down Expand Up @@ -378,6 +385,35 @@ bool StreamWriter::add_encoded_stream(const AVStream *in_stream, const AVCodecCo
encode_ctx[out_stream->index]->codec_tag = MKTAG('h', 'v', 'c', '1');//should be for hevc only?
}

if (cfg.get_value("video-bitrate") != "auto"){
long bitrate = cfg.get_value_long("video-bitrate");
if (bitrate > 1){
encode_ctx[out_stream->index]->bit_rate = bitrate;
}
}

if (!encode_ctx[out_stream->index]->bit_rate){
double framerate = av_q2d(dec_ctx->framerate);
if (framerate < 1){
framerate = 30;
}
double pixel_sec = encode_ctx[out_stream->index]->height * encode_ctx[out_stream->index]->height * framerate;
//estimate the bitrate
if (encode_ctx[out_stream->index]->codec_id == AV_CODEC_ID_HEVC){
encode_ctx[out_stream->index]->bit_rate = pixel_sec / 75;
} else {
//h264
encode_ctx[out_stream->index]->bit_rate = pixel_sec / 40;
}

}

LINFO("Selected video encode bitrate: " + std::to_string(encode_ctx[out_stream->index]->bit_rate/1000) + "kbps");
//apply encode video options
AVDictionary *vopts = Util::get_dict_options(cfg.get_value("ffmpeg-encode-video-opt"));
av_dict_copy(&opts, vopts, NULL);
av_dict_free(&vopts);

//connect encoder
if (!encode_ctx[out_stream->index]->hw_device_ctx &&
(cfg.get_value("video-encode-method") == "auto" || cfg.get_value("video-encode-method") == "vaapi")){
Expand Down Expand Up @@ -407,8 +443,9 @@ bool StreamWriter::add_encoded_stream(const AVStream *in_stream, const AVCodecCo
}

gcff_util.lock();
int ret = avcodec_open2(encode_ctx[out_stream->index], encoder, NULL);
int ret = avcodec_open2(encode_ctx[out_stream->index], encoder, &opts);
gcff_util.unlock();
av_dict_free(&opts);
if (ret < 0) {
LERROR("Cannot open encoder for stream " + std::to_string(out_stream->index));
return false;
Expand All @@ -420,6 +457,7 @@ bool StreamWriter::add_encoded_stream(const AVStream *in_stream, const AVCodecCo
return false;
}


out_stream->time_base = encode_ctx[out_stream->index]->time_base;
return true;
}
Expand Down
141 changes: 78 additions & 63 deletions web/stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,81 +57,96 @@

$yesterday = clone $cur_time;
$yesterday->sub(new DateInterval('P1D'));
//$yesterday->setTime(0,0);
//query the most recent video from the DB
$sql = 'SELECT path, starttime, endtime, extension, name, init_byte, start_byte, end_byte FROM videos '.
'WHERE camera = ' . ((int)$_GET['id']).' AND endtime IS NOT NULL AND starttime > ' . $packed_start;
if ($start_time->getTimestamp() < $yesterday->getTimestamp()){
$endtime = new DateTime('@'.($start_time->getTimestamp() + 3600*24));
$packed_endtime = Datetime_to_dbtime($endtime);
$sql .= ' AND starttime < '. $packed_endtime;
}
$sql .= ' ORDER BY starttime ASC';
$res = $db->query($sql);
if ($res){
$hls_version = 3;
$using_mp4 = false;
if ($camera_cfg->get_value('output-extension') == '.mp4'){
$hls_version = 7;
$using_mp4 = true;
}

//add the header

//Do a master playlist unless a stream is selected
if (!isset($_GET['stream'])){
echo "#EXTM3U\n";
echo "#EXT-X-VERSION:$hls_version\n";
echo '#EXT-X-PROGRAM-DATE-TIME:' . $start_time->format('c') . "\n";
echo "#EXT-X-PLAYLIST-TYPE:EVENT\n";
echo "#EXT-X-TARGETDURATION:" . (2*$camera_cfg->get_value('seconds-per-segment')). "\n";
echo "#EXT-X-INDEPENDENT-SEGMENTS\n";

//master playlist requirements
//EXT-X-STREAM-INF - CODECS and RESOLUTION
$last_endtime = 0;
$last_name = 0;
while ($row = $res->fetch_assoc()){
$url = 'vids/' . $row['path'] . $row['name'] . $row['extension'];
if ($row['starttime'] != $last_endtime){
if ($last_endtime != 0){
echo "#EXT-X-DISCONTINUITY\n";
//MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d0028"')
//MediaSource.isTypeSupported('video/mp4;codecs="avc1.42001f"')
//MediaSource.isTypeSupported('video/mp4;codecs="avc1.640029"')
echo "#EXT-X-STREAM-INF:BANDWIDTH=93000,CODECS=\"avc1.4d0028\",RESOLUTION=640x352,FRAME-RATE=15\n";
$start = (int)$_GET['start'];
$live = (int)$_GET['live'];
echo "stream.php?stream=-1&start=$start&id=$camera&live=$live\n";

} else {
//$yesterday->setTime(0,0);
//query the most recent video from the DB
$sql = 'SELECT path, starttime, endtime, extension, name, init_byte, start_byte, end_byte FROM videos '.
'WHERE camera = ' . ((int)$_GET['id']).' AND endtime IS NOT NULL AND starttime > ' . $packed_start;
if ($start_time->getTimestamp() < $yesterday->getTimestamp()){
$endtime = new DateTime('@'.($start_time->getTimestamp() + 3600*24));
$packed_endtime = Datetime_to_dbtime($endtime);
$sql .= ' AND starttime < '. $packed_endtime;
}
$sql .= ' ORDER BY starttime ASC';
$res = $db->query($sql);
if ($res){
$hls_version = 3;
$using_mp4 = false;
if ($camera_cfg->get_value('output-extension') == '.mp4'){
$hls_version = 7;
$using_mp4 = true;
}

//add the header
echo "#EXTM3U\n";
echo "#EXT-X-VERSION:$hls_version\n";
echo '#EXT-X-PROGRAM-DATE-TIME:' . $start_time->format('c') . "\n";
echo "#EXT-X-PLAYLIST-TYPE:EVENT\n";
echo "#EXT-X-TARGETDURATION:" . (2*$camera_cfg->get_value('seconds-per-segment')). "\n";
echo "#EXT-X-INDEPENDENT-SEGMENTS\n";

//master playlist requirements
//EXT-X-STREAM-INF - CODECS and RESOLUTION
$last_endtime = 0;
$last_name = 0;
while ($row = $res->fetch_assoc()){
$url = 'vids/' . $row['path'] . $row['name'] . $row['extension'];
if ($row['starttime'] != $last_endtime){
if ($last_endtime != 0){
echo "#EXT-X-DISCONTINUITY\n";
}
if ($using_mp4 && $last_name != $row['name'] && $row['extension'] == '.mp4'){
//we just try the first one as our init files
echo "#EXT-X-MAP:URI=\"$url\",BYTERANGE=\"${row['init_byte']}@0\"\n";
}
}
if ($using_mp4 && $last_name != $row['name'] && $row['extension'] == '.mp4'){
//we just try the first one as our init files
echo "#EXT-X-MAP:URI=\"$url\",BYTERANGE=\"${row['init_byte']}@0\"\n";
$len = ($row['endtime'] - $row['starttime'])/1000;
if ($len == 0){//we have seen some with a len of zero...is this an ok workaround?
$len = 0.001;
}
}
$len = ($row['endtime'] - $row['starttime'])/1000;
if ($len == 0){//we have seen some with a len of zero...is this an ok workaround?
$len = 0.001;
}

$name = "Camera $camera: " . dbtime_to_DateTime($row['starttime'])->format('r');
echo "#EXTINF:$len,$name\n";
if ($using_mp4 && $row['extension'] == '.mp4'){
$seg_len = $row['end_byte'] - $row['start_byte'];
echo "#EXT-X-BYTERANGE:{$seg_len}";
if ($last_name != $row['name'] || 1){
echo "@{$row['start_byte']}\n";
} else {
echo "\n";
$name = "Camera $camera: " . dbtime_to_DateTime($row['starttime'])->format('r');
echo "#EXTINF:$len,$name\n";
if ($using_mp4 && $row['extension'] == '.mp4'){
$seg_len = $row['end_byte'] - $row['start_byte'];
echo "#EXT-X-BYTERANGE:{$seg_len}";
if ($last_name != $row['name'] || 1){
echo "@{$row['start_byte']}\n";
} else {
echo "\n";
}
}
}
echo $url . "\n";
$last_endtime = $row['endtime'];
$last_name = $row['name'];
echo $url . "\n";
$last_endtime = $row['endtime'];
$last_name = $row['name'];

}
}

if (!empty($endtime)){
//this is a complete playlist, add the end of the file
echo "#EXT-X-ENDLIST\n";
}
if (!empty($endtime)){
//this is a complete playlist, add the end of the file
echo "#EXT-X-ENDLIST\n";
}

} else {
//query failed
die($sql);
} else {
//query failed
die($sql);
}
}


header('Accept-Ranges: bytes');
function range_explode($str){
global $full_len;
Expand Down

0 comments on commit 66513f6

Please sign in to comment.