Add an audio/video synchronization utility
On Chameleon, audio capturing and video capturing work separately.
To know the time difference between audio/video capturing, we can
monitor the changes of the audio page count and the video frame count.
BUG=None
TEST=manual test.
Use test_server to test StartMonitoringAudioVideoCapturingDelay() and
GetAudioVideoCapturingDelay(), note that the edid should be equipped
with audio.
>>> for i in xrange(10):
... p.StartMonitoringAudioVideoCapturingDelay()
... p.StartCapturingVideo(3)
... p.StartCapturingAudio(3)
... time.sleep(2)
... p.StopCapturingVideo()
... p.StopCapturingAudio(3)
... print p.GetAudioVideoCapturingDelay()
...
Change-Id: I9b213964afed51a2e9c7b9c56ddc8b6002ef73bd
Reviewed-on: https://chromium-review.googlesource.com/371158
Commit-Ready: Chen-hao Chang <haocc@google.com>
Tested-by: Chen-hao Chang <haocc@google.com>
Reviewed-by: Wai-Hong Tam <waihong@google.com>
diff --git a/Makefile b/Makefile
index 37ffde8..0f3a794 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,8 @@
directories:
@mkdir -p $(BINDIR)
-BINARIES = $(BINDIR)/histogram $(BINDIR)/hpd_control $(BINDIR)/pixeldump
+BINARIES = $(BINDIR)/histogram $(BINDIR)/hpd_control $(BINDIR)/pixeldump \
+ $(BINDIR)/avsync
.PHONY: binaries
binaries: $(BINARIES)
diff --git a/chameleond/drivers/fpga_tio.py b/chameleond/drivers/fpga_tio.py
index 40eb2e8..99f13c4 100644
--- a/chameleond/drivers/fpga_tio.py
+++ b/chameleond/drivers/fpga_tio.py
@@ -107,6 +107,7 @@
self._selected_input = None
self._selected_output = None
self._captured_params = {}
+ self._process = None
# Reserve index 0 as the default EDID.
self._all_edids = [self._ReadDefaultEdid()]
@@ -556,6 +557,47 @@
self._selected_output = port_id
self._flows[port_id].Do_FSM()
+ def StartMonitoringAudioVideoCapturingDelay(self):
+ """Starts an audio/video synchronization utility
+
+ The example of usage:
+ chameleon.StartMonitoringAudioVideoCapturingDelay()
+ chameleon.StartCapturingVideo(hdmi_input)
+ chameleon.StartCapturingAudio(hdmi_input)
+ time.sleep(2)
+ chameleon.StopCapturingVideo()
+ chameleon.StopCapturingAudio(hdmi_input)
+ delay = chameleon.GetAudioVideoCapturingDelay()
+ """
+ self._process = system_tools.SystemTools.RunInSubprocess('avsync')
+
+ def GetAudioVideoCapturingDelay(self):
+ """Get the time interval between the first audio/video cpatured data
+
+ Returns:
+ A floating points indicating the time interval between the first
+ audio/video data captured. If the result is negative, then the first
+ video data is earlier, otherwise the first audio data is earlier.
+
+ Raises:
+ DriverError if there is no output from the monitoring process.
+ """
+
+ if self._process.poll() == None:
+ self._process.terminate()
+ raise DriverError('The monitoring process has not finished.')
+
+ return_code, out, err = system_tools.SystemTools.GetSubprocessOutput(
+ self._process)
+
+ if return_code != 0 or err:
+ raise DriverError('Runtime error in the monitoring process')
+
+ if not out:
+ raise DriverError('No output from the monitoring process.')
+
+ return float(out)
+
@_VideoMethod
def DumpPixels(self, port_id, x=None, y=None, width=None, height=None):
"""Dumps the raw pixel array of the selected area.
@@ -839,7 +881,7 @@
value of GetCapturedFrameCount.
Returns:
- The list of checksums of frames.
+ The list of histograms of frames.
"""
return self._GetCapturedSignals('GetHistograms', start_index, stop_index)
diff --git a/chameleond/utils/memory_dumper.py b/chameleond/utils/memory_dumper.py
index c8341a6..9394c4d 100644
--- a/chameleond/utils/memory_dumper.py
+++ b/chameleond/utils/memory_dumper.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -6,11 +5,7 @@
import logging
import multiprocessing
-import subprocess
-import sys
-import time
-from chameleond.utils import mem
from chameleond.utils import system_tools
@@ -35,6 +30,7 @@
self._last_page_count = None
self._last_last_page_count = None
self._current_page_count = None
+ self._page_count_in_this_period = None
self._file_path = file_path
self._adump = adump
self._stop_event = multiprocessing.Event()
@@ -45,8 +41,13 @@
self._stop_event.set()
def run(self):
- """Runs the periodic dumping process."""
- self._last_page_count = self._adump.GetCurrentPageCount()
+ """Runs the periodic dumping process.
+
+ Note that the audio page count should really start from 0, and this
+ should not be called too lately, or the page count in the first period
+ may be greater than MAX_DUMP_PAGES.
+ """
+ self._last_page_count = 0
while True:
self._stop_event.wait(self._DUMP_PERIOD_SECS)
if self._stop_event.is_set():
@@ -177,7 +178,7 @@
start_address = (self._adump.start_address +
start_page_index * self._adump.PAGE_SIZE)
- command = ['pixeldump', '-a', start_address, '-' , self._adump.PAGE_SIZE,
+ command = ['pixeldump', '-a', start_address, '-', self._adump.PAGE_SIZE,
page_count, 1]
logging.info('Dump: %s', command)
p = system_tools.SystemTools.RunInSubprocess(*command)
diff --git a/chameleond/utils/system_tools.py b/chameleond/utils/system_tools.py
index 0d7192a..7771c69 100644
--- a/chameleond/utils/system_tools.py
+++ b/chameleond/utils/system_tools.py
@@ -14,6 +14,7 @@
_TOOL_PATHS = {
'aplay': '/usr/bin/aplay',
'arecord': '/usr/bin/arecord',
+ 'avsync': '/usr/bin/avsync',
'chameleond': '/etc/init.d/chameleond',
'date': '/bin/date',
'i2cdump': '/usr/local/sbin/i2cdump',
diff --git a/src/avsync.c b/src/avsync.c
new file mode 100644
index 0000000..805a2cb
--- /dev/null
+++ b/src/avsync.c
@@ -0,0 +1,117 @@
+// Copyright 2016 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* Audio Video Capturing synchronization utility
+ *
+ * This is a command-line tool running on chameleon board to monitor the
+ * changes of the audio page count and the video page count, and calcuate the
+ * time interval between the first audio/video data captured.
+ */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+const int controller_addr = 0xff210000;
+const int controller_size = 0x10000;
+
+char* mem;
+
+inline int read_mem(int addr)
+{
+ return *(int*)(mem + (addr - controller_addr));
+}
+
+inline int audio_page_count()
+{
+ const int audio_regs_base = 0xff212000;
+ const int audio_reg_page_count = 0x14;
+
+ return read_mem(audio_regs_base + audio_reg_page_count);
+}
+
+inline int video_field_count()
+{
+ const int video_regs_base = 0xff210000;
+ const int video_reg_frame_count = 0x20;
+
+ return read_mem(video_regs_base + video_reg_frame_count);
+}
+
+int main()
+{
+ const int fd = open("/dev/mem", O_RDONLY | O_SYNC);
+ if (fd == -1) {
+ perror("open");
+ exit(1);
+ }
+
+ mem = mmap(NULL, controller_size, PROT_READ, MAP_SHARED, fd, controller_addr);
+ if (mem == MAP_FAILED) {
+ perror("mmap");
+ exit(1);
+ }
+
+ int last_audio_page_count = audio_page_count();
+ int last_video_frame_count = video_field_count();
+
+ struct timeval ta, tv;
+ bool done_audio = false, done_video = false;
+ time_t timeout = time(NULL) + 20;
+ while ((!done_audio || !done_video) && time(NULL) < timeout) {
+ if (!done_audio) {
+ const int current_audio_page_count = audio_page_count();
+ if (current_audio_page_count > last_audio_page_count) {
+ gettimeofday(&ta, NULL);
+ done_audio = true;
+ }
+ last_audio_page_count = current_audio_page_count;
+ }
+ /*
+ * In chameleond, VideoDumper will capture 1 frame when it selects a new
+ * input, so the change of the frame count from 0 to 1 may be originated
+ * from that frame, and the second captured frame is always the frame
+ * we care about.
+ */
+ if (!done_video) {
+ const int current_video_frame_count = video_field_count();
+ if (current_video_frame_count > last_video_frame_count &&
+ current_video_frame_count >= 2) {
+ gettimeofday(&tv, NULL);
+ done_video = true;
+ }
+ last_video_frame_count = current_video_frame_count;
+ }
+ usleep(100);
+ }
+
+ if (!done_audio || !done_video) {
+ return -1;
+ }
+
+ /*
+ * Because tv is the time when the second frame was captured, to estimate
+ * the time when the first frame was captured, we need to shift it by -1/60
+ * second.
+ */
+ const double diff = (tv.tv_sec - ta.tv_sec) +
+ (tv.tv_usec - ta.tv_usec) * 1e-6 -
+ 1.0 / 60;
+
+ printf("%.8f\n", diff);
+
+ munmap(mem, controller_size);
+ close(fd);
+
+ return 0;
+}