2019-03-07 23:08:01 +00:00
|
|
|
"""Multiplexes video and audio inputs to encoded byte stream."""
|
|
|
|
|
|
|
|
from typing import Iterable, Iterator
|
|
|
|
|
2019-03-21 16:24:40 +00:00
|
|
|
import audio
|
2019-03-21 16:48:02 +00:00
|
|
|
import machine
|
2019-03-21 16:24:40 +00:00
|
|
|
import opcodes
|
|
|
|
import video
|
2019-03-07 23:08:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Movie:
|
2019-03-21 22:43:02 +00:00
|
|
|
def __init__(
|
|
|
|
self, filename: str,
|
|
|
|
every_n_video_frames: int = 1,
|
2019-03-23 21:46:35 +00:00
|
|
|
audio_normalization: float = None,
|
2019-04-25 16:38:04 +00:00
|
|
|
max_bytes_out: int = None,
|
|
|
|
video_mode: video.Mode = video.Mode.HGR,
|
2019-03-23 21:46:35 +00:00
|
|
|
):
|
2019-03-07 23:08:01 +00:00
|
|
|
self.filename = filename # type: str
|
2019-03-21 22:43:02 +00:00
|
|
|
self.every_n_video_frames = every_n_video_frames # type: int
|
2019-03-23 21:46:35 +00:00
|
|
|
self.max_bytes_out = max_bytes_out # type: int
|
2019-04-25 16:38:04 +00:00
|
|
|
self.video_mode = video_mode # type: video.Mode
|
2019-03-23 21:46:35 +00:00
|
|
|
|
2019-03-07 23:08:01 +00:00
|
|
|
self.audio = audio.Audio(
|
|
|
|
filename, normalization=audio_normalization) # type: audio.Audio
|
2019-04-25 16:38:04 +00:00
|
|
|
self.video = video.Video(filename, mode=video_mode) # type: video.Video
|
2019-03-07 23:08:01 +00:00
|
|
|
|
|
|
|
self.stream_pos = 0 # type: int
|
|
|
|
|
2019-03-21 22:43:02 +00:00
|
|
|
# TODO: don't use this as well as cycle_counter, it's a relic of when
|
|
|
|
# I relied on variable-duration opcodes for frame timings.
|
|
|
|
self.cycles = 0 # type: int
|
2019-03-21 16:48:02 +00:00
|
|
|
self.cycle_counter = machine.CycleCounter()
|
2019-03-07 23:08:01 +00:00
|
|
|
|
2019-03-21 16:48:02 +00:00
|
|
|
self.state = machine.Machine(
|
2019-03-07 23:08:01 +00:00
|
|
|
self.cycle_counter,
|
|
|
|
self.video.memory_map,
|
|
|
|
self.video.update_priority
|
|
|
|
)
|
|
|
|
|
2019-03-27 21:37:06 +00:00
|
|
|
self.aux_memory_bank = False
|
|
|
|
|
2019-03-07 23:08:01 +00:00
|
|
|
def encode(self) -> Iterator[opcodes.Opcode]:
|
2019-03-21 22:43:02 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
:return:
|
|
|
|
"""
|
2019-03-10 22:42:31 +00:00
|
|
|
video_frames = self.video.frames()
|
2019-04-25 16:38:04 +00:00
|
|
|
main_seq = None
|
|
|
|
aux_seq = None
|
2019-04-25 15:28:06 +00:00
|
|
|
|
2019-04-25 16:38:04 +00:00
|
|
|
yield opcodes.Header(mode=self.video_mode)
|
2019-03-07 23:08:01 +00:00
|
|
|
|
|
|
|
for au in self.audio.audio_stream():
|
|
|
|
self.cycles += self.audio.cycles_per_tick
|
2019-03-10 22:42:31 +00:00
|
|
|
if self.video.tick(self.cycles):
|
2019-03-27 21:37:06 +00:00
|
|
|
main, aux = next(video_frames)
|
2019-03-21 22:43:02 +00:00
|
|
|
if ((self.video.frame_number - 1) % self.every_n_video_frames
|
|
|
|
== 0):
|
|
|
|
print("Starting frame %d" % self.video.frame_number)
|
2019-03-27 21:37:06 +00:00
|
|
|
main_seq = self.video.encode_frame(
|
|
|
|
main, self.video.memory_map, self.video.update_priority)
|
2019-04-25 16:38:04 +00:00
|
|
|
|
|
|
|
if aux:
|
|
|
|
aux_seq = self.video.encode_frame(
|
|
|
|
aux, self.video.aux_memory_map,
|
|
|
|
self.video.aux_update_priority)
|
2019-03-07 23:08:01 +00:00
|
|
|
|
|
|
|
# au has range -15 .. 16 (step=1)
|
|
|
|
# Tick cycles are units of 2
|
|
|
|
tick = au * 2 # -30 .. 32 (step=2)
|
|
|
|
tick += 34 # 4 .. 66 (step=2)
|
|
|
|
|
2019-03-27 21:37:06 +00:00
|
|
|
(page, content, offsets) = next(
|
|
|
|
aux_seq if self.aux_memory_bank else main_seq)
|
2019-03-07 23:08:01 +00:00
|
|
|
|
|
|
|
yield opcodes.TICK_OPCODES[(tick, page)](content, offsets)
|
|
|
|
|
|
|
|
def _emit_bytes(self, _op):
|
2019-03-21 22:43:02 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
:param _op:
|
|
|
|
:return:
|
|
|
|
"""
|
2019-03-14 23:05:47 +00:00
|
|
|
for b in self.state.emit(_op):
|
2019-03-07 23:08:01 +00:00
|
|
|
yield b
|
|
|
|
self.stream_pos += 1
|
|
|
|
|
|
|
|
def emit_stream(self, ops: Iterable[opcodes.Opcode]) -> Iterator[int]:
|
2019-03-21 22:43:02 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
:param ops:
|
|
|
|
:return:
|
|
|
|
"""
|
2019-03-07 23:08:01 +00:00
|
|
|
for op in ops:
|
2019-03-24 00:14:41 +00:00
|
|
|
if self.max_bytes_out and self.stream_pos >= self.max_bytes_out:
|
2019-03-23 21:46:35 +00:00
|
|
|
yield from self.done()
|
|
|
|
return
|
2019-06-12 20:37:30 +00:00
|
|
|
|
|
|
|
yield from self._emit_bytes(op)
|
|
|
|
|
2019-03-07 23:08:01 +00:00
|
|
|
# Keep track of where we are in TCP client socket buffer
|
|
|
|
socket_pos = self.stream_pos % 2048
|
|
|
|
if socket_pos >= 2044:
|
2019-03-28 23:47:38 +00:00
|
|
|
# 2 op_ack address bytes + 2 payload bytes from ACK must
|
|
|
|
# terminate 2K stream frame
|
2019-04-25 16:38:04 +00:00
|
|
|
if self.video_mode == video.Mode.DHGR:
|
|
|
|
# Flip-flop between MAIN and AUX banks
|
|
|
|
self.aux_memory_bank = not self.aux_memory_bank
|
|
|
|
|
2019-03-27 21:37:06 +00:00
|
|
|
yield from self._emit_bytes(opcodes.Ack(self.aux_memory_bank))
|
2019-04-25 15:28:06 +00:00
|
|
|
assert self.stream_pos % 2048 == 0, self.stream_pos % 2048
|
2019-04-25 16:38:04 +00:00
|
|
|
|
2019-03-24 00:14:41 +00:00
|
|
|
yield from self.done()
|
|
|
|
|
2019-03-07 23:08:01 +00:00
|
|
|
def done(self) -> Iterator[int]:
|
2019-03-21 22:43:02 +00:00
|
|
|
"""Terminate opcode stream.
|
|
|
|
|
|
|
|
:return:
|
|
|
|
"""
|
2019-03-07 23:08:01 +00:00
|
|
|
yield from self._emit_bytes(opcodes.Terminate())
|
2019-03-23 21:46:35 +00:00
|
|
|
|
|
|
|
# Player expects to fill 2K TCP buffer so pad it out
|
|
|
|
for _ in range(2048 - (self.stream_pos % 2048)):
|
|
|
|
yield 0x00
|