parser for new screen recording metadata
Bug: 235196806 Test: cd development/tools/winscope-ng && npm run build:unit && npm run test:unit Change-Id: Ie964b30f2f88a35ce428fab9fe1da192599da6e5
This commit is contained in:
@@ -81,15 +81,74 @@ describe("ArrayUtils", () => {
|
||||
it("toUintLittleEndian", () => {
|
||||
const buffer = new Uint8Array([0, 0, 1, 1]);
|
||||
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 0, -1)).toEqual(0);
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 0, 0)).toEqual(0);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 0, -1)).toEqual(0n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 0, 0)).toEqual(0n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 1, 1)).toEqual(0n);
|
||||
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 0, 1)).toEqual(0);
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 0, 2)).toEqual(0);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x01, 0xff]), 0, 1)).toEqual(0n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x01, 0xff]), 1, 2)).toEqual(1n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x01, 0xff]), 2, 3)).toEqual(255n);
|
||||
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 3, 4)).toEqual(1);
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 2, 4)).toEqual(1 + 256);
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 1, 4)).toEqual(256 + 256*256);
|
||||
expect(ArrayUtils.toUintLittleEndian(buffer, 0, 3)).toEqual(256*256);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x00]), 0, 2)).toEqual(0n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x01, 0x00]), 0, 2)).toEqual(1n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x01]), 0, 2)).toEqual(256n);
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 0, 2)).toEqual(0xffffn);
|
||||
|
||||
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(0xffffffffn);
|
||||
|
||||
expect(
|
||||
ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 8))
|
||||
.toEqual(0xffffffffffffffffn);
|
||||
|
||||
expect(
|
||||
ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 9))
|
||||
.toEqual(0xffffffffffffffffffn);
|
||||
});
|
||||
|
||||
it("toIntLittleEndian", () => {
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff]), 0, -1)).toEqual(0n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff]), 0, 0)).toEqual(0n);
|
||||
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x00]), 0, 1)).toEqual(0n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01]), 0, 1)).toEqual(1n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x7f]), 0, 1)).toEqual(127n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x80]), 0, 1)).toEqual(-128n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff]), 0, 1)).toEqual(-1n);
|
||||
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0x7f]), 0, 2)).toEqual(32767n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x80]), 0, 2)).toEqual(-32768n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x80]), 0, 2)).toEqual(-32767n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff]), 0, 2)).toEqual(-1n);
|
||||
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0x7f]), 0, 4)).toEqual(0x7fffffffn);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x80]), 0, 4)).toEqual(-0x80000000n);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x80]), 0, 4)).toEqual(-0x7fffffffn);
|
||||
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(-1n);
|
||||
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]), 0, 8)
|
||||
).toEqual(0x7fffffffffffffffn);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 8)
|
||||
).toEqual(-0x8000000000000000n);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 8)
|
||||
).toEqual(-0x7fffffffffffffffn);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 8)
|
||||
).toEqual(-1n);
|
||||
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]), 0, 9)
|
||||
).toEqual(0x7fffffffffffffffffn);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 9)
|
||||
).toEqual(-0x800000000000000000n);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 9)
|
||||
).toEqual(-0x7fffffffffffffffffn);
|
||||
expect(
|
||||
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 9)
|
||||
).toEqual(-1n);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,14 +86,30 @@ class ArrayUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
static toUintLittleEndian(buffer: Uint8Array, start: number, end: number) {
|
||||
let result = 0;
|
||||
for (let i = end-1; i>=start; --i) {
|
||||
result *= 256;
|
||||
result += buffer[i];
|
||||
static toUintLittleEndian(buffer: Uint8Array, start: number, end: number): bigint {
|
||||
let result = 0n;
|
||||
for (let i = end-1; i >= start; --i) {
|
||||
result *= 256n;
|
||||
result += BigInt(buffer[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static toIntLittleEndian(buffer: Uint8Array, start: number, end: number): bigint {
|
||||
const numOfBits = BigInt(Math.max(0, 8 * (end-start)));
|
||||
if (numOfBits <= 0n) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
let result = ArrayUtils.toUintLittleEndian(buffer, start, end);
|
||||
const maxSignedValue = 2n ** (numOfBits - 1n) - 1n;
|
||||
if (result > maxSignedValue) {
|
||||
const valuesRange = 2n ** numOfBits;
|
||||
result -= valuesRange;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export {ArrayUtils};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {ParserInputMethodManagerService} from "./parser_input_method_manager_ser
|
||||
import {ParserInputMethodService} from "./parser_input_method_service";
|
||||
import {ParserProtoLog} from "./parser_protolog";
|
||||
import {ParserScreenRecording} from "./parser_screen_recording";
|
||||
import {ParserScreenRecordingLegacy} from "./parser_screen_recording_legacy";
|
||||
import {ParserSurfaceFlinger} from "./parser_surface_flinger";
|
||||
import {ParserTransactions} from "./parser_transactions";
|
||||
import {ParserWindowManager} from "./parser_window_manager";
|
||||
@@ -33,6 +34,7 @@ class ParserFactory {
|
||||
ParserInputMethodService,
|
||||
ParserProtoLog,
|
||||
ParserScreenRecording,
|
||||
ParserScreenRecordingLegacy,
|
||||
ParserSurfaceFlinger,
|
||||
ParserTransactions,
|
||||
ParserWindowManager,
|
||||
|
||||
@@ -23,8 +23,8 @@ describe("ParserScreenRecording", () => {
|
||||
let parser: Parser;
|
||||
|
||||
beforeAll(async () => {
|
||||
const buffer = TestUtils.getFixtureBlob("screen_recording.mp4");
|
||||
const parsers = await new ParserFactory().createParsers([buffer]);
|
||||
const trace = TestUtils.getFixtureBlob("screen_recording.mp4");
|
||||
const parsers = await new ParserFactory().createParsers([trace]);
|
||||
expect(parsers.length).toEqual(1);
|
||||
parser = parsers[0];
|
||||
});
|
||||
@@ -37,26 +37,23 @@ describe("ParserScreenRecording", () => {
|
||||
const timestamps = parser.getTimestamps();
|
||||
|
||||
expect(timestamps.length)
|
||||
.toEqual(85);
|
||||
.toEqual(88);
|
||||
|
||||
expect(timestamps.slice(0, 3))
|
||||
.toEqual([19446131807000, 19446158500000, 19446167117000]);
|
||||
|
||||
expect(timestamps.slice(timestamps.length-3, timestamps.length))
|
||||
.toEqual([19448470076000, 19448487525000, 19448501007000]);
|
||||
.toEqual([1658843852566916400, 1658843852889741300, 1658843852901528300]);
|
||||
});
|
||||
|
||||
it("retrieves trace entry", () => {
|
||||
{
|
||||
const entry = parser.getTraceEntry(19446131807000)!;
|
||||
const entry = parser.getTraceEntry(1658843852566916400)!;
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
|
||||
}
|
||||
|
||||
{
|
||||
const entry = parser.getTraceEntry(19448501007000)!;
|
||||
const entry = parser.getTraceEntry(1658843852889741300)!;
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0.322, 0.001);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,11 @@ import {ArrayUtils} from "common/utils/array_utils";
|
||||
import {Parser} from "./parser";
|
||||
import {ScreenRecordingTraceEntry} from "common/trace/screen_recording";
|
||||
|
||||
class ScreenRecordingMetadataEntry {
|
||||
constructor(public timestampMonotonicNs: bigint, public timestampRealtimeNs: bigint) {
|
||||
}
|
||||
}
|
||||
|
||||
class ParserScreenRecording extends Parser {
|
||||
constructor(trace: Blob) {
|
||||
super(trace);
|
||||
@@ -31,20 +36,29 @@ class ParserScreenRecording extends Parser {
|
||||
return ParserScreenRecording.MPEG4_MAGIC_NMBER;
|
||||
}
|
||||
|
||||
override decodeTrace(videoData: Uint8Array): number[] {
|
||||
const posCount = this.searchMagicString(videoData);
|
||||
const [posTimestamps, count] = this.parseTimestampsCount(videoData, posCount);
|
||||
return this.parseTimestamps(videoData, posTimestamps, count);
|
||||
override decodeTrace(videoData: Uint8Array): ScreenRecordingMetadataEntry[] {
|
||||
const posVersion = this.searchMagicString(videoData);
|
||||
const [posTimeOffset, metadataVersion] = this.parseMetadataVersion(videoData, posVersion);
|
||||
if (metadataVersion !== 1) {
|
||||
throw TypeError(`Metadata version "${metadataVersion}" not supported`);
|
||||
}
|
||||
const [posCount, timeOffsetNs] = this.parseRealToMonotonicTimeOffsetNs(videoData, posTimeOffset);
|
||||
const [posTimestamps, count] = this.parseFramesCount(videoData, posCount);
|
||||
const timestampsMonotonicNs = this.parseTimestampsMonotonicNs(videoData, posTimestamps, count);
|
||||
|
||||
return timestampsMonotonicNs.map((timestampMonotonicNs: bigint) => {
|
||||
return new ScreenRecordingMetadataEntry(timestampMonotonicNs, timestampMonotonicNs + timeOffsetNs);
|
||||
});
|
||||
}
|
||||
|
||||
override getTimestamp(decodedEntry: number): number {
|
||||
return decodedEntry;
|
||||
override getTimestamp(decodedEntry: ScreenRecordingMetadataEntry): number {
|
||||
return Number(decodedEntry.timestampRealtimeNs);
|
||||
}
|
||||
|
||||
override processDecodedEntry(timestamp: number): ScreenRecordingTraceEntry {
|
||||
const videoTimeSeconds = (timestamp - this.timestamps[0]) / 1000000000 + ParserScreenRecording.EPSILON;
|
||||
override processDecodedEntry(entry: ScreenRecordingMetadataEntry): ScreenRecordingTraceEntry {
|
||||
const videoTimeSeconds = (Number(entry.timestampRealtimeNs) - this.timestamps[0]) / 1000000000;
|
||||
const videoData = this.trace;
|
||||
return new ScreenRecordingTraceEntry(timestamp, videoTimeSeconds, videoData);
|
||||
return new ScreenRecordingTraceEntry(Number(entry.timestampRealtimeNs), videoTimeSeconds, videoData);
|
||||
}
|
||||
|
||||
private searchMagicString(videoData: Uint8Array): number {
|
||||
@@ -56,22 +70,42 @@ class ParserScreenRecording extends Parser {
|
||||
return pos;
|
||||
}
|
||||
|
||||
private parseTimestampsCount(videoData: Uint8Array, pos: number) : [number, number] {
|
||||
if (pos + 4 >= videoData.length) {
|
||||
throw new TypeError("video data is too short. Expected timestamps count doesn't fit");
|
||||
private parseMetadataVersion(videoData: Uint8Array, pos: number) : [number, number] {
|
||||
if (pos + 4 > videoData.length) {
|
||||
throw new TypeError("Failed to parse metadata version. Video data is too short.");
|
||||
}
|
||||
const timestampsCount = ArrayUtils.toUintLittleEndian(videoData, pos, pos+4);
|
||||
const version = Number(ArrayUtils.toUintLittleEndian(videoData, pos, pos+4));
|
||||
pos += 4;
|
||||
return [pos, timestampsCount];
|
||||
return [pos, version];
|
||||
}
|
||||
|
||||
private parseTimestamps(videoData: Uint8Array, pos: number, count: number): number[] {
|
||||
if (pos + count * 8 >= videoData.length) {
|
||||
throw new TypeError("video data is too short. Expected timestamps do not fit");
|
||||
private parseRealToMonotonicTimeOffsetNs(videoData: Uint8Array, pos: number) : [number, bigint] {
|
||||
if (pos + 8 > videoData.length) {
|
||||
throw new TypeError("Failed to parse realtime-to-monotonic time offset. Video data is too short.");
|
||||
}
|
||||
const timestamps: number[] = [];
|
||||
const offset = ArrayUtils.toIntLittleEndian(videoData, pos, pos+8);
|
||||
pos += 8;
|
||||
return [pos, offset];
|
||||
}
|
||||
|
||||
private parseFramesCount(videoData: Uint8Array, pos: number) : [number, number] {
|
||||
if (pos + 4 > videoData.length) {
|
||||
throw new TypeError("Failed to parse frames count. Video data is too short.");
|
||||
}
|
||||
const count = Number(ArrayUtils.toUintLittleEndian(videoData, pos, pos+4));
|
||||
pos += 4;
|
||||
return [pos, count];
|
||||
}
|
||||
|
||||
private parseTimestampsMonotonicNs(videoData: Uint8Array, pos: number, count: number) : bigint[] {
|
||||
if (pos + count * 16 > videoData.length) {
|
||||
throw new TypeError("Failed to parse monotonic timestamps. Video data is too short.");
|
||||
}
|
||||
const timestamps: bigint[] = [];
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const timestamp = ArrayUtils.toUintLittleEndian(videoData, pos, pos+8) * 1000;
|
||||
const timestamp = ArrayUtils.toUintLittleEndian(videoData, pos, pos+8);
|
||||
pos += 8;
|
||||
//parse VSYNC ID here when available
|
||||
pos += 8;
|
||||
timestamps.push(timestamp);
|
||||
}
|
||||
@@ -79,8 +113,7 @@ class ParserScreenRecording extends Parser {
|
||||
}
|
||||
|
||||
private static readonly MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32]; // ....ftypmp42
|
||||
private static readonly WINSCOPE_META_MAGIC_STRING = [0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 0x4d, 0x45, 0x21, 0x23]; // #VV1NSC0PET1ME!#
|
||||
private static readonly EPSILON = 0.00001;
|
||||
private static readonly WINSCOPE_META_MAGIC_STRING = [0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 0x4d, 0x45, 0x32, 0x23]; // #VV1NSC0PET1ME2#
|
||||
}
|
||||
|
||||
export {ParserScreenRecording};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ScreenRecordingTraceEntry} from "common/trace/screen_recording";
|
||||
import {TraceTypeId} from "common/trace/type_id";
|
||||
import {TestUtils} from "test/test_utils";
|
||||
import {Parser} from "./parser";
|
||||
import {ParserFactory} from "./parser_factory";
|
||||
|
||||
describe("ParserScreenRecordingLegacy", () => {
|
||||
let parser: Parser;
|
||||
|
||||
beforeAll(async () => {
|
||||
const trace = TestUtils.getFixtureBlob("screen_recording_legacy.mp4");
|
||||
const parsers = await new ParserFactory().createParsers([trace]);
|
||||
expect(parsers.length).toEqual(1);
|
||||
parser = parsers[0];
|
||||
});
|
||||
|
||||
it("has expected trace type", () => {
|
||||
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.SCREEN_RECORDING);
|
||||
});
|
||||
|
||||
it("provides timestamps", () => {
|
||||
const timestamps = parser.getTimestamps();
|
||||
|
||||
expect(timestamps.length)
|
||||
.toEqual(85);
|
||||
|
||||
expect(timestamps.slice(0, 3))
|
||||
.toEqual([19446131807000, 19446158500000, 19446167117000]);
|
||||
|
||||
expect(timestamps.slice(timestamps.length-3, timestamps.length))
|
||||
.toEqual([19448470076000, 19448487525000, 19448501007000]);
|
||||
});
|
||||
|
||||
it("retrieves trace entry", () => {
|
||||
{
|
||||
const entry = parser.getTraceEntry(19446131807000)!;
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
|
||||
}
|
||||
|
||||
{
|
||||
const entry = parser.getTraceEntry(19448501007000)!;
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {TraceTypeId} from "common/trace/type_id";
|
||||
import {ArrayUtils} from "common/utils/array_utils";
|
||||
import {Parser} from "./parser";
|
||||
import {ScreenRecordingTraceEntry} from "common/trace/screen_recording";
|
||||
|
||||
class ParserScreenRecordingLegacy extends Parser {
|
||||
constructor(trace: Blob) {
|
||||
super(trace);
|
||||
}
|
||||
|
||||
override getTraceTypeId(): TraceTypeId {
|
||||
return TraceTypeId.SCREEN_RECORDING;
|
||||
}
|
||||
|
||||
override getMagicNumber(): number[] {
|
||||
return ParserScreenRecordingLegacy.MPEG4_MAGIC_NMBER;
|
||||
}
|
||||
|
||||
override decodeTrace(videoData: Uint8Array): number[] {
|
||||
const posCount = this.searchMagicString(videoData);
|
||||
const [posTimestamps, count] = this.parseFramesCount(videoData, posCount);
|
||||
return this.parseTimestamps(videoData, posTimestamps, count);
|
||||
}
|
||||
|
||||
override getTimestamp(decodedEntry: number): number {
|
||||
return decodedEntry;
|
||||
}
|
||||
|
||||
override processDecodedEntry(timestamp: number): ScreenRecordingTraceEntry {
|
||||
const videoTimeSeconds = (timestamp - this.timestamps[0]) / 1000000000 + ParserScreenRecordingLegacy.EPSILON;
|
||||
const videoData = this.trace;
|
||||
return new ScreenRecordingTraceEntry(timestamp, videoTimeSeconds, videoData);
|
||||
}
|
||||
|
||||
private searchMagicString(videoData: Uint8Array): number {
|
||||
let pos = ArrayUtils.searchSubarray(videoData, ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING);
|
||||
if (pos === undefined) {
|
||||
throw new TypeError("video data doesn't contain winscope magic string");
|
||||
}
|
||||
pos += ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING.length;
|
||||
return pos;
|
||||
}
|
||||
|
||||
private parseFramesCount(videoData: Uint8Array, pos: number) : [number, number] {
|
||||
if (pos + 4 > videoData.length) {
|
||||
throw new TypeError("Failed to parse frames count. Video data is too short.");
|
||||
}
|
||||
const framesCount = Number(ArrayUtils.toUintLittleEndian(videoData, pos, pos+4));
|
||||
pos += 4;
|
||||
return [pos, framesCount];
|
||||
}
|
||||
|
||||
private parseTimestamps(videoData: Uint8Array, pos: number, count: number): number[] {
|
||||
if (pos + count * 8 > videoData.length) {
|
||||
throw new TypeError("Failed to parse timestamps. Video data is too short.");
|
||||
}
|
||||
const timestamps: number[] = [];
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const timestamp = Number(ArrayUtils.toUintLittleEndian(videoData, pos, pos+8) * 1000n);
|
||||
pos += 8;
|
||||
timestamps.push(timestamp);
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
private static readonly MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32]; // ....ftypmp42
|
||||
private static readonly WINSCOPE_META_MAGIC_STRING = [0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 0x4d, 0x45, 0x21, 0x23]; // #VV1NSC0PET1ME!#
|
||||
private static readonly EPSILON = 0.00001;
|
||||
}
|
||||
|
||||
export {ParserScreenRecordingLegacy};
|
||||
Binary file not shown.
BIN
tools/winscope-ng/src/test/fixtures/screen_recording_legacy.mp4
vendored
Normal file
BIN
tools/winscope-ng/src/test/fixtures/screen_recording_legacy.mp4
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user