Commit 48bf333c authored by Shik Chen's avatar Shik Chen Committed by Commit Bot

CCA: Produce non-fragmented MP4 if the output is seekable

Guides FFmpeg to generate regular (non-fragmented) MP4 if the output is
seekable.

Bug: 1140852
Test: Pass camera.CCAUIRecordVideo and camera.CCAUIIntent
Change-Id: Icc1e32c9160e4734360a35738479b47345a7ab95
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513810Reviewed-by: default avatarInker Kuo <inker@chromium.org>
Commit-Queue: Shik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823473}
parent 478cfe06
......@@ -30,9 +30,9 @@ let FileStream; // eslint-disable-line no-unused-vars
let FileOps; // eslint-disable-line no-unused-vars
/**
* An emulated stdin device backed by Int8Array.
* An emulated input device backed by Int8Array.
*/
class StdinDevice {
class InputDevice {
/**
* @public
*/
......@@ -151,9 +151,9 @@ class StdinDevice {
}
/**
* An emulated stdout device.
* An emulated output device.
*/
class StdoutDevice {
class OutputDevice {
/**
* @param {!AsyncWriter} output Where should the device write to.
*/
......@@ -185,11 +185,29 @@ class StdoutDevice {
const blob = new Blob([buffer.subarray(offset, offset + length)]);
assert(
position === undefined || position === stream.position,
'stdout is not seekable');
'combined seek-and-write operation is not supported');
this.output_.write(blob);
return length;
}
/**
* Implements the llseek() operation for the emulated device.
* Only SEEK_SET (0) is supported as |whence|. Reference:
* https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.llseek
* @param {!FileStream} stream
* @param {number} offset The offset in bytes relative to |whence|.
* @param {number} whence The reference position to be used.
* @return {number} The resulting file position.
*/
llseek(stream, offset, whence) {
assert(whence === 0, 'only SEEK_SET is supported');
assert(this.output_.seekable());
if (stream.position !== offset) {
this.output_.seek(offset);
}
return offset;
}
/**
* Implements the close() operation for the emulated device.
*/
......@@ -211,9 +229,9 @@ class StdoutDevice {
return {
open: () => {},
close: this.close.bind(this),
read: () => assertNotReached('read should not be called on stdout'),
read: () => assertNotReached('read should not be called on output'),
write: this.write.bind(this),
llseek: () => assertNotReached('llseek should not be called on stdout'),
llseek: this.llseek.bind(this),
};
}
}
......@@ -224,30 +242,42 @@ class StdoutDevice {
class Mp4VideoProcessor {
/**
* @param {!AsyncWriter} output The output writer of mp4.
* @param {{seekable: boolean}} opts
*/
constructor(output) {
constructor(output, {seekable}) {
this.output_ = output;
this.stdin_ = new StdinDevice();
this.stdout_ = new StdoutDevice(output);
this.inputDevice_ = new InputDevice();
this.outputDevice_ = new OutputDevice(output);
this.jobQueue_ = new AsyncJobQueue();
const args = [
// Make the procssing pipeline start earlier by shorten the initial
// analyze durtaion from the default 5s to 1s. This reduce the
// stop-capture lantency significantly for short videos.
'-analyzeduration', '1M',
// mkv input from stdin
'-f', 'matroska', '-i', 'pipe:0',
// transcode audio to aac and copy the video
'-c:a', 'aac', '-c:v', 'copy',
// show error log only
'-hide_banner', '-loglevel', 'error',
// do not ask anything
'-nostdin', '-y' // eslint-disable-line comma-dangle
];
// TODO(crbug.com/1140852): Remove non-seekable code path once the Android
// camera intent helper support seek operation.
if (!seekable) {
// Mark unseekable.
args.push('-seekable', '0');
// Produce a fragmented MP4.
args.push('-movflags', 'frag_keyframe', '-frag_duration', '100000');
}
args.push('/output.mp4');
const config = {
arguments: [
// Make the procssing pipeline start earlier by shorten the initial
// analyze durtaion from the default 5s to 1s. This reduce the
// stop-capture lantency significantly for short videos.
'-analyzeduration', '1M',
// mkv input from stdin
'-f', 'matroska', '-i', 'pipe:0',
// transcode audio to aac and copy the video
'-c:a', 'aac', '-c:v', 'copy',
// verbose and don't ask anything
'-hide_banner', '-loglevel', 'error', '-nostdin', '-y',
// streaming friendly output
'-movflags', 'frag_keyframe', '-frag_duration', '100000',
// mp4 output to stdout
'-f', 'mp4', 'pipe:1' // eslint-disable-line comma-dangle
],
arguments: args,
locateFile: (file) => {
assert(file === 'ffmpeg.wasm');
return '/js/lib/ffmpeg.wasm';
......@@ -262,11 +292,14 @@ class Mp4VideoProcessor {
// major numbers 1, 3, 5, 6, 64, and 65. Ref:
// https://github.com/emscripten-core/emscripten/blob/1ed6dd5cfb88d927ec03ecac8756f0273810d5c9/src/library_fs.js#L1331
const input = fs.makedev(80, 0);
fs.registerDevice(input, this.stdin_.getFileOps());
fs.registerDevice(input, this.inputDevice_.getFileOps());
fs.mkdev('/dev/stdin', input);
const output = fs.makedev(80, 1);
fs.registerDevice(output, this.stdout_.getFileOps());
fs.mkdev('/dev/stdout', output);
fs.registerDevice(output, this.outputDevice_.getFileOps());
fs.mkdev('/output.mp4', output);
fs.symlink('/dev/tty1', '/dev/stdout');
fs.symlink('/dev/tty1', '/dev/stderr');
const stdin = fs.open('/dev/stdin', 'r');
const stdout = fs.open('/dev/stdout', 'w');
......@@ -291,7 +324,7 @@ class Mp4VideoProcessor {
// This is a function to be called by ffmpeg before running read() in C.
globalThis.waitReadable = (callback) => {
this.stdin_.setReadableCallback(callback);
this.inputDevice_.setReadableCallback(callback);
};
}
......@@ -302,7 +335,7 @@ class Mp4VideoProcessor {
write(blob) {
this.jobQueue_.push(async () => {
const buf = await blob.arrayBuffer();
this.stdin_.push(new Int8Array(buf));
this.inputDevice_.push(new Int8Array(buf));
});
}
......@@ -311,14 +344,14 @@ class Mp4VideoProcessor {
* @return {!Promise} Resolved when all write operations are finished.
*/
async close() {
// Flush and close stdin.
// Flush and close the input device.
this.jobQueue_.push(async () => {
this.stdin_.endPush();
this.inputDevice_.endPush();
});
await this.jobQueue_.flush();
// Wait until stdout is closed.
await this.stdout_.waitClosed();
// Wait until the output device is closed.
await this.outputDevice_.waitClosed();
// Flush and close the output writer.
await this.output_.close();
......
......@@ -31,7 +31,8 @@ const VideoProcessor = (async () => {
*/
async function createVideoProcessor(output) {
// Comlink proxies all calls asynchronously, including constructors.
return new (await VideoProcessor)(Comlink.proxy(output));
return new (await VideoProcessor)(
Comlink.proxy(output), {seekable: output.seekable()});
}
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment