Skip to content

Instantly share code, notes, and snippets.

@btoconnor
Created October 19, 2023 01:07
Show Gist options
  • Save btoconnor/381f9d48ed0bedeae955ae0012b36455 to your computer and use it in GitHub Desktop.
Save btoconnor/381f9d48ed0bedeae955ae0012b36455 to your computer and use it in GitHub Desktop.
diff --git a/tvapp/tuner/antenna.py b/tvapp/tuner/antenna.py
index f6c9615..f34105b 100644
--- a/tvapp/tuner/antenna.py
+++ b/tvapp/tuner/antenna.py
@@ -85,119 +85,136 @@ class AntennaTuner(Tuner):
"WMPB", # PBS
"CW", # CW
]
def __init__(self, url, prefix, type_, channels, channel_file_location):
"""Initialize Antenna tuner.
We just pass in the adapter number to pass to dvbv5-zap as url.
It's not great, but whatever. Instead of passing in a raw integer to
the base Tuner class, just pass the original string so that it's more
obvious in logs / debugging.
"""
Tuner.__init__(self, "unused_str_adapter_%s" % url, prefix, type_)
self.encoder_cmd = None
self.tuner_cmd = None
+ self.monitor_cmd = None
self.channel_proc = None
self.ffmpeg_proc = None
+ self.monitor_proc = None
self.adapter_number = url
self.channels = channels
log.debug("Channels: %s", self.channels)
self.channel_file_location = channel_file_location
def change_stream(self, req_channel, req_quality):
"""This is the entry point
For the direct antenna tuner, we need to construct one big command
that instructs dvbv5-zap to enter record mode and pipe the output
directly to the input of ffmpeg.
As far as I can tell, this means that much like the HDHR tuner,
when the channel or the quality is changed we'll need to restart
ffmpeg.
"""
tuner_cmd = self._build_channel_command(req_channel)
encoder_cmd = self._build_encoder_command(req_channel, req_quality)
+ monitor_cmd = self._build_monitor_command()
logging.info("Changing channel to {}".format(req_channel))
# If either the tuner command or the encoder command has changed, we're
# going to restart the process.
if (tuner_cmd != self.tuner_cmd) or (encoder_cmd != self.encoder_cmd):
# Restart ffmpeg.
log.debug("Changing stream requires stopping!")
self.tuner_cmd = tuner_cmd
self.encoder_cmd = encoder_cmd
+ self.monitor_cmd = monitor_cmd
self.stop_stream()
def _build_tunerproc(self):
# We're piping the output of dvbv5-zap directly to ffmpeg, so we need
# to create our own tunerproc. This overrides what is happening
# in the base tuner.
default_channel = self._default_channel()
default_quality = 4
if self.tuner_cmd is None:
self.tuner_cmd = self._build_channel_command(default_channel)
if self.encoder_cmd is None:
self.encoder_cmd = self._build_encoder_command(default_channel, default_quality)
+ if self.monitor_cmd is None:
+ self.monitor_cmd = self._build_monitor_command()
+
self.channel_proc = subprocess.Popen(self.tuner_cmd, stdout=subprocess.PIPE)
self.ffmpeg_proc = subprocess.Popen(
self.encoder_cmd,
stdin=self.channel_proc.stdout,
stderr=subprocess.PIPE
)
+ self.monitor_proc = subprocess.Popen(
+ self.monitor_cmd,
+ stderr=subprocess.PIPE
+ )
+
return self.ffmpeg_proc
def _stop_tuner_proc(self):
"""Stop the processes associated with the stream.
Overriding because the antenna tuner has two procs and the
base tuner isn't handling that. There might be a better way than this
but I'm only looking to see if this works.
"""
if self.ffmpeg_proc.poll() is None:
self.ffmpeg_proc.send_signal(subprocess.signal.SIGTERM)
if self.channel_proc.poll() is None:
self.channel_proc.send_signal(subprocess.signal.SIGTERM)
+ if self.monitor_proc.poll() is None:
+ self.monitor_proc.send_signal(subprocess.signal.SIGTERM)
+
st = time.time()
- for proc in [self.ffmpeg_proc, self.channel_proc]:
+ for proc in [self.ffmpeg_proc, self.channel_proc, self.monitor_proc]:
while True:
# Kill the process if it takes longer than 3 seconds
# to terminate gracefully
if proc.poll() is not None:
break
if time.time() - st > 3.0:
proc.send_signal(subprocess.signal.SIGKILL)
- self.stop_was_kill = True # Do we need to update this??
+ self.stop_was_kill = True # Do we need to update this??
break
self.ffmpeg_proc.wait()
self.channel_proc.wait()
+ self.monitor_proc.wait()
self.ffmpeg_proc = None
self.channel_proc = None
+ self.monitor_proc = None
# Need to set this as well because this is what the base tuner
# is watching to detect if needs to stop the tuner procs.
self.tunerproc = None
def validate_channel(self, channel):
# Make sure the requested channel is in the configuration file.
if channel not in self.channels:
log.info("Channel %s not in %s", channel, self.channels)
return self._default_channel()
return channel
def _build_channel_command(self, channel):
"Construct command for dvbv5-zap"
@@ -230,30 +247,38 @@ class AntennaTuner(Tuner):
"-i", "-",
]
if quality is None:
quality = self.quality
command += self._build_video_opts(channel, quality)
command += self._build_audio_opts(quality)
command += self._build_audio_channel_opts(quality)
command += self._build_subtitle_opts()
command += self.hlscmds
return command
+ def _build_monitor_command(self):
+ # dvb-fe-tool -a 0 -m
+ return [
+ "dvb-fe-tool",
+ "-a", self.adapter_number,
+ "-m"
+ ]
+
def _build_audio_channel_opts(self, quality):
"Build ffmpeg command opts for audio channels"
num_audio_channels = self.AUDIO_CHANNELS[quality]
return [
"-ac", num_audio_channels
]
def _build_video_opts(self, channel, quality):
"Build ffmpeg command opts for video encoding"
# Values from http://www.lighterra.com/papers/videoencodingh264/
opts = []
video_bitrate = self.VIDEO_QUALITIES[quality]
video_size = self.VIDEO_RESOLUTIONS_P[quality]
diff --git a/tvapp/tuner/base.py b/tvapp/tuner/base.py
index fb10b38..9a12e96 100644
--- a/tvapp/tuner/base.py
+++ b/tvapp/tuner/base.py
@@ -167,30 +167,35 @@ class Tuner:
# self.tunerproc.wait()
# self.tunerproc = None
subprocess.call("rm " + STREAMDIR+self.prefix+"*", shell=True)
self.status = 'Stopped'
def check_stream(self, curtime):
if self.tunerproc.poll() is not None:
# Stream died
self.stop_stream(abnormal=True)
time.sleep(1)
return
res = select.select([self.tunerproc.stderr], [], [], 0.1)
if res[0]:
self.updatetime = curtime
self.tunerproc.stderr.read()
+
+ # TODO: Grab output from monitor proc for CNR and SNR values
+ # Sample message:
+ # `Lock (0x1f) Signal= -13.00dBm C/N= 23.20dB`
+
return
if (curtime - self.updatetime > FREEZETIME
and curtime - self.start_time > FREEZESTARTTIME):
# Tuner got stuck - restart it
logging.info(
"Stopping stream because frozen: curtime: {}, updatetime: {}, start time: {}".format(
curtime, self.updatetime, self.start_time
)
)
self.stop_stream(abnormal=True)
# Tuner customization
def prepare_stream(self):
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment