Skip to content

Commit

Permalink
Merge pull request #808 from happycube/ac3-test1
Browse files Browse the repository at this point in the history
Merge in (early) AC3 support
  • Loading branch information
happycube authored Oct 29, 2022
2 parents 36724f1 + 665c045 commit 1848d20
Show file tree
Hide file tree
Showing 40 changed files with 3,761 additions and 328 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ add_subdirectory(tools/ld-discmap)
add_subdirectory(tools/ld-dropout-correct)
add_subdirectory(tools/ld-export-metadata)
add_subdirectory(tools/ld-lds-converter)
add_subdirectory(tools/ld-process-ac3)
add_subdirectory(tools/ld-process-efm)
add_subdirectory(tools/ld-process-vbi)
add_subdirectory(tools/ld-process-vits)
Expand Down
20 changes: 16 additions & 4 deletions ld-decode
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ parser.add_argument(
default=False,
help="Disable analog(ue) audio decoding",
)
parser.add_argument(
"--AC3",
action="store_true",
default=False,
help="Enable AC3 audio decoding (NTSC only)",
)
parser.add_argument(
"--start_fileloc",
metavar="start_fileloc",
Expand Down Expand Up @@ -272,6 +278,7 @@ extra_options = {
"deemp_mult": (args.deemp_adjust, args.deemp_adjust),
"deemp_coeff": (args.deemp_low, args.deemp_high),
"audio_filterwidth": args.audio_filterwidth,
"AC3": args.AC3,
}

if vid_standard == "NTSC" and args.NTSC_color_notch_filter:
Expand All @@ -280,6 +287,13 @@ if vid_standard == "NTSC" and args.NTSC_color_notch_filter:
if vid_standard == "PAL" and args.V4300D_notch_filter:
extra_options["PAL_V4300D_NotchFilter"] = True

if vid_standard == "PAL" and args.V4300D_notch_filter:
extra_options["PAL_V4300D_NotchFilter"] = True

if vid_standard == "PAL" and args.AC3:
print("ERROR: AC3 audio decoding is only supported for NTSC")
exit(1)

if args.lowband:
extra_options["lowband"] = True

Expand All @@ -289,8 +303,6 @@ except ValueError as e:
print(e)
exit(1)

system = "PAL" if args.pal else "NTSC"

# Wrap the LDdecode creation so that the signal handler is not taken by sub-threads,
# allowing SIGINT/control-C's to be handled cleanly
original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
Expand All @@ -308,7 +320,7 @@ ldd = LDdecode(
est_frames=req_frames,
analog_audio=0 if args.daa else args.analog_audio_freq,
digital_audio=not args.noefm,
system=system,
system=vid_standard,
doDOD=not args.nodod,
threads=args.threads,
extra_options=extra_options,
Expand All @@ -321,7 +333,7 @@ if args.start_fileloc != -1:
else:
ldd.roughseek(firstframe * 2)

if system == "NTSC" and not args.ntscj:
if vid_standard == "NTSC" and not args.ntscj:
ldd.blackIRE = 7.5

# print(ldd.blackIRE)
Expand Down
54 changes: 50 additions & 4 deletions lddecode/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
# internal libraries

from . import efm_pll
from .utils import get_git_info, ldf_pipe, traceback
from .utils import get_git_info, ac3_pipe, ldf_pipe, traceback
from .utils import nb_mean, nb_median, nb_round, nb_min, nb_max, nb_absmax
from .utils import polar2z, sqsum, genwave, dsa_rescale_and_clip, scale, rms
from .utils import findpeaks, findpulses, calczc, inrange, roundfloat
from .utils import LRUupdate, clb_findnextburst, angular_mean, phase_distance
from .utils import build_hilbert, unwrap_hilbert, emphasis_iir, filtfft
from .utils import fft_do_slice, fft_determine_slices
from .utils import fft_do_slice, fft_determine_slices, StridedCollector

try:
# If Anaconda's numpy is installed, mkl will use all threads for fft etc
Expand Down Expand Up @@ -84,8 +84,9 @@ def calclinelen(SP, mult, mhz):
"analog_audio": True,
# From the spec - audio frequencies are multiples of the (color) line rate
"audio_lfreq": (1000000 * 315 / 88 / 227.5) * 146.25,
# NOTE: this changes to 2.88mhz on AC3 disks
"audio_rfreq": (1000000 * 315 / 88 / 227.5) * 178.75,
# On AC3 disks, the right channel is replaced by a QPSK 2.88mhz channel
"audio_rfreq_AC3": 2880000,
"colorBurstUS": (5.3, 7.8),
"activeVideoUS": (9.45, 63.555 - 1.0),
# Known-good area for computing black SNR - for NTSC pull from VSYNC
Expand Down Expand Up @@ -298,6 +299,7 @@ def __init__(
- PAL_V4300D_NotchFilter - cut 8.5mhz spurious signal
- NTSC_ColorNotchFilter: notch filter on decoded video to reduce color 'wobble'
- lowband: Substitute different decode settings for lower-bandwidth disks
- AC3: Supports AC3
"""

Expand Down Expand Up @@ -333,6 +335,9 @@ def __init__(
self.DecoderParams = copy.deepcopy(RFParams_PAL)

self.SysParams["analog_audio"] = has_analog_audio
self.SysParams["AC3"] = extra_options.get("AC3", False)
if self.SysParams["AC3"]:
self.SysParams["audio_rfreq"] = self.SysParams["audio_rfreq_AC3"]

fw = extra_options.get("audio_filterwidth", 0)
if fw is not None and fw > 0:
Expand Down Expand Up @@ -379,6 +384,17 @@ def computefilters(self):
if self.decode_digital_audio:
self.computeefmfilter()

if self.SysParams['AC3']:
apass = 288000
self.Filters['AC3_fir'] = sps.firwin(161,
[
(self.SysParams['audio_rfreq_AC3'] - apass) / self.freq_hz_half,
(self.SysParams['audio_rfreq_AC3'] + apass) / self.freq_hz_half,
],
pass_zero=False)

self.Filters['AC3'] = filtfft((self.Filters['AC3_fir'], [1.0]), self.blocklen)

self.computedelays()

def computeefmfilter(self):
Expand Down Expand Up @@ -733,6 +749,7 @@ def demodblock(self, data=None, mtf_level=0, fftdata=None, cut=False):
efm_out = efm_out[self.blockcut : -self.blockcut_end]
rv["efm"] = np.int16(np.clip(efm_out.real, -32768, 32767))

# NOTE: ac3 audio is filtered after RF TBC
if self.decode_analog_audio:
stage1_out = []
for channel in ['left', 'right']:
Expand Down Expand Up @@ -3275,6 +3292,7 @@ def __init__(

self.analog_audio = int(analog_audio)
self.digital_audio = digital_audio
self.ac3 = extra_options.get("AC3", False)
self.write_rf_tbc = extra_options.get("write_RF_TBC", False)

self.has_analog_audio = True
Expand All @@ -3290,7 +3308,9 @@ def __init__(
self.outfile_audio = None
self.outfile_efm = None
self.outfile_pre_efm = None
self.outfile_ac3 = None
self.ffmpeg_rftbc, self.outfile_rftbc = None, None
self.do_rftbc = False

if fname_out is not None:
self.outfile_video = open(fname_out + ".tbc", "wb")
Expand All @@ -3304,8 +3324,15 @@ def __init__(
self.outfile_pre_efm = open(fname_out + ".prefm", "wb")
if self.write_rf_tbc:
self.ffmpeg_rftbc, self.outfile_rftbc = ldf_pipe(fname_out + ".tbc.ldf")
self.do_rftbc = True
if self.ac3:
self.AC3Collector = StridedCollector(cut_begin=1024, cut_end=0)
self.ac3_processes, self.outfile_ac3 = ac3_pipe(fname_out + ".ac3")
self.do_rftbc = True

self.pipe_rftbc = extra_options.get("pipe_RF_TBC", None)
if self.pipe_rftbc:
self.do_rftbc = True

self.fname_out = fname_out

Expand Down Expand Up @@ -3388,6 +3415,7 @@ def close(self):
"outfile_json",
"outfile_efm",
"outfile_rftbc",
"outfile_ac3",
]:
setattr(self, outfiles, None)

Expand Down Expand Up @@ -3440,11 +3468,26 @@ def detectLevels(self, field):

return np.median(sync_hzs), np.median(ire0_hzs)

def AC3filter(self, rftbc):
self.AC3Collector.add(rftbc)

blk = self.AC3Collector.get_block()
while blk is not None:
fftdata = np.fft.fft(blk)
filtdata = np.fft.ifft(fftdata * self.rf.Filters['AC3']).real
odata = self.AC3Collector.cut(filtdata)
odata = np.int8(odata / 32)

self.outfile_ac3.write(odata)

blk = self.AC3Collector.get_block()

def writeout(self, dataset):
f, fi, picture, audio, efm = dataset
if self.digital_audio is True:
if self.outfile_pre_efm is not None:
self.outfile_pre_efm.write(efm.tobytes())

efm_out = self.efm_pll.process(efm)
self.outfile_efm.write(efm_out.tobytes())

Expand All @@ -3456,12 +3499,15 @@ def writeout(self, dataset):
self.outfile_video.write(picture)
self.fields_written += 1

if self.outfile_rftbc is not None or self.pipe_rftbc is not None:
if self.do_rftbc:
rftbc = f.rf_tbc()

if self.outfile_rftbc is not None:
self.outfile_rftbc.write(rftbc)

if self.outfile_ac3 is not None:
self.AC3filter(rftbc)

if self.pipe_rftbc is not None:
self.pipe_rftbc.send(rftbc)

Expand Down
38 changes: 35 additions & 3 deletions lddecode/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ def __call__(self, infile, sample, readlen):
return self.read(infile, sample, readlen)


def ldf_pipe(outname, compression_level=6):
def ldf_pipe(outname: str, compression_level: int = 6):
corecmd = "ffmpeg -y -hide_banner -loglevel error -f s16le -ar 40k -ac 1 -i - -acodec flac -f ogg".split(
" "
)
Expand All @@ -537,6 +537,30 @@ def ldf_pipe(outname, compression_level=6):

return process, process.stdin

def ac3_pipe(outname: str):
processes = []

cmd1 = "sox -r 40000000 -b 8 -c 1 -e signed -t raw - -b 8 -r 46080000 -e unsigned -c 1 -t raw -"
cmd2 = "ld-ac3-demodulate -v 3 - -"
cmd3 = f"ld-ac3-decode - {outname}"

logfp = open(f"{outname + '.log'}", 'w')

# This is done in reverse order to allow for pipe building
processes.append(subprocess.Popen(cmd3.split(' '),
stdin=subprocess.PIPE,
stdout=logfp,
stderr=subprocess.STDOUT))

processes.append(subprocess.Popen(cmd2.split(' '),
stdin=subprocess.PIPE,
stdout=processes[-1].stdin))

processes.append(subprocess.Popen(cmd1.split(' '),
stdin=subprocess.PIPE,
stdout=processes[-1].stdin))

return processes, processes[-1].stdin

# Git helpers

Expand Down Expand Up @@ -1069,10 +1093,13 @@ def consume(q):
class StridedCollector:
# This keeps a numpy buffer and outputs an fft block and keeps the overlap
# for the next fft.
def __init__(self, blocklen=65536, stride=2048):
def __init__(self, blocklen=32768, cut_begin=2048, cut_end=0):
self.buffer = None
self.blocklen = blocklen
self.stride = stride

self.cut_begin = cut_begin
self.cut_end = self.blocklen - cut_end
self.stride = cut_begin + cut_end

def add(self, data):
if self.buffer is None:
Expand All @@ -1085,6 +1112,11 @@ def add(self, data):
def have_block(self):
return (self.buffer is not None) and (len(self.buffer) >= self.blocklen)

def cut(self, processed_data):
# TODO: assert len(processed_data) == self.blocklen

return processed_data[self.cut_begin : self.cut_end]

def get_block(self):
if self.have_block():
rv = self.buffer[0 : self.blocklen]
Expand Down
Loading

0 comments on commit 1848d20

Please sign in to comment.