Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream2 #658

Merged
merged 3 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions ld-decode
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
#!/usr/bin/env python3
from base64 import b64encode
import copy
from datetime import datetime
import getopt
import io
from io import BytesIO
import logging
import os
import signal
import subprocess
import sys
import argparse
import json
import traceback

from multiprocessing import Process, Pool, Queue, JoinableQueue, Pipe
import threading
import queue
from multiprocessing import Process, Pipe

import lddecode.audio as audio
from lddecode.core import *
Expand Down
39 changes: 15 additions & 24 deletions lddecode/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,11 @@
# Draft of new-audio code

import argparse
import copy
import itertools
import sys
import threading
import time

from functools import partial
from multiprocessing import Process, Queue, JoinableQueue, Pipe

# standard numeric/scientific libraries
import numpy as np
from numpy.lib.function_base import _parse_gufunc_signature
import scipy.signal as sps
import scipy.interpolate as spi

# Use PyFFTW's faster FFT implementation if available
try:
Expand Down Expand Up @@ -47,7 +38,7 @@
from lddecode import utils

try:
import utils_logging
import utils_logging
except ImportError:
from lddecode import utils_logging

Expand All @@ -72,7 +63,7 @@ def audio_bandpass_butter(center, closerange = 125000, longrange = 180000):
''' Returns filter coefficients for first stage per-channel filtering '''
freqs_inner = [(center - closerange) / freq_hz_half, (center + closerange) / freq_hz_half]
freqs_outer = [(center - longrange) / freq_hz_half, (center + longrange) / freq_hz_half]

N, Wn = sps.buttord(freqs_inner, freqs_outer, 1, 15)

return sps.butter(N, Wn, btype='bandpass')
Expand Down Expand Up @@ -103,11 +94,11 @@ def __init__(self, freq, center_freq, vid_standard):

# Add the demodulated output to this to get actual freq
self.low_freq = self.freq_hz * (lowbin / blocklen)

self.slicer = lambda x: utils.fft_do_slice(x, lowbin, nbins, blocklen)
self.filt1 = self.slicer(audio1_fir) * hilbert
self.filt1f = audio1_fir * utils.build_hilbert(blocklen)

self.audio1_buffer = utils.StridedCollector(blocklen, blockskip)
self.audio1_clip = blockskip // (blocklen // nbins)

Expand Down Expand Up @@ -186,7 +177,7 @@ def __init__(self, args):

if True:
self.aa_channels.append(AudioRF(self.freq, SysParams['audio_lfreq'], args.vid_standard))

if True:
self.aa_channels.append(AudioRF(self.freq, SysParams['audio_rfreq'], args.vid_standard))

Expand Down Expand Up @@ -220,16 +211,16 @@ def process_input_buffer(self):
o = output + channel.low_freq - channel.center_freq
#print(np.mean(o), np.std(o), channel.low_freq, channel.center_freq)
ofloat.append(np.clip((o / 150000), -16, 16).astype(np.float32))

if len(outputs) == 2:
#print(len(outputs), np.mean(o32[0]), np.std(o32[0]), np.std(o32[1]))

outdata = np.zeros(len(ofloat[0]) * 2, dtype=np.float32)
outdata[0::2] = ofloat[0]
outdata[1::2] = ofloat[1]
else:
outdata = np.array(ofloat[0], dtype=np.float32)

if self.out_fd is not None:
self.out_fd.write(outdata)

Expand All @@ -242,13 +233,13 @@ def process_input_buffer(self):

efm_out = self.efm_pll_object.process(filtered_efm2)
#print(efm_out.shape, max(filtered_efm.imag))

if self.efm_fd is not None:
#print(len(buf), len(filtered_efm2), len(efm_out), file=sys.stderr)
self.efm_fd.write(efm_out.tobytes())

def startprocess(inpipe, args):
''' Hook for Multiprocessing.Process()
''' Hook for Multiprocessing.Process()
procargs is a tuple containing the input pipe and args from ld-decode
'''
args.vid_standard = "PAL" if args.pal else "NTSC"
Expand All @@ -268,11 +259,11 @@ def startprocess(inpipe, args):
ad.input_buffer.add(data_int8)

ad.process_input_buffer()

if __name__ == "__main__":
# Standalone command line front end code

# NOTE: These arguments must be consistent with top-level ld-decode, since
# NOTE: These arguments must be consistent with top-level ld-decode, since
# they can be passed from it as well

def handle_options(argstring = sys.argv):
Expand All @@ -284,7 +275,7 @@ def handle_options(argstring = sys.argv):
parser.add_argument("-i", dest="infile", default='-', type=str, help="source file (must be signed 16-bit)")

parser.add_argument("-o", dest="outfile", default='test', type=str, help="base name for destination files")

parser.add_argument("-f", "--freq", dest='inputfreq', default = "40.0mhz", type=utils.parse_frequency, help="Input frequency")

parser.add_argument(
Expand Down Expand Up @@ -325,7 +316,7 @@ def handle_options(argstring = sys.argv):
default=False,
help="Write filtered but otherwise pre-processed EFM data",
)

args = parser.parse_args(argstring[1:])

if args.pal and args.ntsc:
Expand Down Expand Up @@ -356,7 +347,7 @@ def handle_options(argstring = sys.argv):

if len(inbuf) == 0:
break

# store the input buffer as a raw 8-bit data, then repack into 16-bit
# (this allows reading of an odd # of bytes)
ad.input_buffer.add(np.frombuffer(inbuf, 'int8', len(inbuf)))
Expand Down
77 changes: 41 additions & 36 deletions lddecode/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,21 +235,21 @@ def calclinelen(SP, mult, mhz):


class RFDecode:
"""The core RF decoding code.
This decoder uses FFT overlap-save processing(1) to allow for parallel processing and combination of
"""The core RF decoding code.
This decoder uses FFT overlap-save processing(1) to allow for parallel processing and combination of
operations.
Video filter signal path:
- FFT/iFFT stage 1: RF BPF (i.e. 3.5-13.5mhz NTSC) * hilbert filter
- phase unwrapping
- FFT stage 2, which is processed into multiple final products:
- Regular video output
- 0.5mhz LPF (used for HSYNC)
- For fine-tuning HSYNC: NTSC: 3.5x mhz filtered signal, PAL: 3.75mhz pilot signal
Analogue audio filter signal path:
The audio signal path is actually more complex in some ways, since it reduces a
multi-msps signal down to <100khz. A two stage processing system is used which
reduces the frequency in each stage.
Expand Down Expand Up @@ -516,8 +516,8 @@ def computevideofilters(self):
# Using an FIR filter here to get a known delay
F0_5 = sps.firwin(65, [0.5 / self.freq_half], pass_zero=True)
SF["F05_offset"] = 32
SF["F05"] = filtfft((F0_5, [1.0]), self.blocklen)
SF["FVideo05"] = SF["Fvideo_lpf"] * SF["Fdeemp"] * SF["F05"]
F0_5_fft = filtfft((F0_5, [1.0]), self.blocklen)
SF["FVideo05"] = SF["Fvideo_lpf"] * SF["Fdeemp"] * F0_5_fft

SF["Fburst"] = filtfft(
sps.butter(
Expand Down Expand Up @@ -700,11 +700,11 @@ def demodblock(self, data=None, mtf_level=0, fftdata=None, cut=False):
]

if self.system == "PAL" and self.PAL_V4300D_NotchFilter:
""" This routine works around an 'interesting' issue seen with LD-V4300D players and
""" This routine works around an 'interesting' issue seen with LD-V4300D players and
some PAL digital audio disks, where there is a signal somewhere between 8.47 and 8.57mhz.
The idea here is to look for anomolies (3 std deviations) and snip them out of the
FFT. There may be side effects, however, but generally minor compared to the
FFT. There may be side effects, however, but generally minor compared to the
'wibble' itself and only in certain cases.
"""
sl = slice(
Expand Down Expand Up @@ -949,7 +949,7 @@ def audio_phase2(self, field_audio):

def computedelays(self, mtf_level=0):
"""Generate a fake signal and compute filter delays.
mtf_level -- Specify the amount of MTF compensation needed (default 0.0)
WARNING: May not actually work.
"""
Expand Down Expand Up @@ -1480,9 +1480,9 @@ def get_linelen(self, line=None, linelocs=None):
# If this is run early, line locations are unknown, so return
# the general value
if linelocs is None:
try:
if hasattr(self, 'linelocs'):
linelocs = self.linelocs
except:
else:
return self.rf.linelen

if line is None:
Expand Down Expand Up @@ -1805,7 +1805,7 @@ def processVBlank(self, validpulses, start, limit=None):
firstblank, lastblank = self.getBlankRange(validpulses, start)
conf = 100

"""
"""
First Look at each equalization/vblank pulse section - if the expected # are there and valid,
it can be used to determine where line 0 is...
"""
Expand Down Expand Up @@ -2468,7 +2468,7 @@ def downscale(
return dsout, self.dsaudio, self.efmout

def rf_tbc(self, linelocs=None):
""" This outputs a TBC'd version of the input RF data, mostly intended
""" This outputs a TBC'd version of the input RF data, mostly intended
to assist in audio processing. Outputs a uint16 array.
"""

Expand Down Expand Up @@ -2592,7 +2592,7 @@ def dropout_detect_demod(self):
iserr3 |= f.data["video"]["demod_05"] > valid_max05

iserr = iserr1 | iserr2 | iserr3 | iserr_rf

# Each valid pulse is definitely *not* an error, so exclude it here at the end
for v in self.validpulses:
iserr[
Expand Down Expand Up @@ -2824,7 +2824,7 @@ def refine_linelocs_pilot(self, linelocs=None):

def get_burstlevel(self, l, linelocs=None):
lineslice = self.lineslice(l, 5.5, 2.4, linelocs)

burstarea = self.data["video"]["demod"][lineslice].copy()
burstarea -= nb_mean(burstarea)

Expand Down Expand Up @@ -2856,14 +2856,14 @@ def determine_field_number(self):

""" Background
PAL has an eight field sequence that can be split into two four field sequences.
Field 1: First field of frame , no colour burst on line 6
Field 2: Second field of frame, colour burst on line 6 (319)
Field 3: First field of frame, colour burst on line 6
Field 4: Second field of frame, no colour burst on line 6 (319)
Fields 5-8 can be differentiated using the burst phase on line 7+4x (based off the first
line guaranteed to have colour burst) Ideally the rising phase would be at 0 or 180
Fields 5-8 can be differentiated using the burst phase on line 7+4x (based off the first
line guaranteed to have colour burst) Ideally the rising phase would be at 0 or 180
degrees, but since this is Laserdisc it's often quite off. So the determination is
based on which phase is closer to 0 degrees.
"""
Expand Down Expand Up @@ -2959,7 +2959,7 @@ def __init__(self, field):
self.cbuffer = self.buildCBuffer()

def getlinephase(self, line):
""" determine if a line has positive color burst phase.
""" determine if a line has positive color burst phase.
This is based on line # and field phase ID """
fieldID = self.field.fieldPhaseID

Expand All @@ -2969,10 +2969,10 @@ def getlinephase(self, line):
return (fieldID == 2) | (fieldID == 3)

def buildCBuffer(self, subset=None):
"""
"""
prev_field: Compute values for previous field
subset: a slice computed by lineslice_tbc (default: whole field)
subset: a slice computed by lineslice_tbc (default: whole field)
NOTE: first and last two returned values will be zero, so slice accordingly
"""

Expand All @@ -2995,10 +2995,10 @@ def buildCBuffer(self, subset=None):
return cbuffer

def splitIQ_line(self, cbuffer, line=0):
"""
"""
NOTE: currently? only works on one line
This returns normalized I and Q arrays, each one half the length of cbuffer
This returns normalized I and Q arrays, each one half the length of cbuffer
"""
linephase = self.getlinephase(line)

Expand Down Expand Up @@ -3062,7 +3062,8 @@ def get_burstlevel(self, l, linelocs=None):
# Issue #621 - fields w/skips may be complete nonsense, so bail out if so
try:
return rms(burstarea) * np.sqrt(2)
except:
except Exception:
# Should we warn here? (Provided this can actually occur.)
return 0

def compute_burst_offsets(self, linelocs):
Expand Down Expand Up @@ -3105,7 +3106,10 @@ def refine_linelocs_burst(self, linelocs=None):

try:
adjs[l] = adjs_new[l] * lfreq * (1 / self.rf.SysParams["fsc_mhz"])
except:
except Exception:
# Not sure if this is an error or just control flow.
# print("Something went wrong when trying to compute line length adjustments...", file=sys.stderr)
# traceback.print_exc()
pass

if len(adjs.keys()):
Expand Down Expand Up @@ -3320,10 +3324,11 @@ def __del__(self):
def close(self):
""" deletes all open files, so it's possible to pickle an LDDecode object """

try:
self.ffmpeg_rftbc.kill()
except:
pass
if self.ffmpeg_rftbc is not None:
try:
self.ffmpeg_rftbc.kill()
except Exception:
pass

# use setattr to force file closure by unlinking the objects
for outfiles in [
Expand Down Expand Up @@ -3387,7 +3392,6 @@ def detectLevels(self, field):

def writeout(self, dataset):
f, fi, picture, audio, efm = dataset

if self.digital_audio == True:
if self.outfile_pre_efm is not None:
self.outfile_pre_efm.write(efm.tobytes())
Expand Down Expand Up @@ -3893,8 +3897,9 @@ def buildmetadata(self, f):
if not self.earlyCLV:
fi["clvSeconds"] = int(self.clvSeconds)
fi["clvFrameNr"] = int(self.clvFrameNum)
except:
except Exception:
logger.warning("file frame %d : VBI decoding error", rawloc)
traceback.print_exc()

return fi, False

Expand Down
Loading