1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {ArrayUtils} from 'common/array_utils'; 18import {ScreenRecordingTraceEntry} from 'trace/screen_recording'; 19import {Timestamp, TimestampType} from 'trace/timestamp'; 20import {TraceFile} from 'trace/trace_file'; 21import {TraceType} from 'trace/trace_type'; 22import {AbstractParser} from './abstract_parser'; 23 24class ParserScreenRecordingLegacy extends AbstractParser { 25 constructor(trace: TraceFile) { 26 super(trace); 27 } 28 29 override getTraceType(): TraceType { 30 return TraceType.SCREEN_RECORDING; 31 } 32 33 override getMagicNumber(): number[] { 34 return ParserScreenRecordingLegacy.MPEG4_MAGIC_NMBER; 35 } 36 37 override decodeTrace(videoData: Uint8Array): Timestamp[] { 38 const posCount = this.searchMagicString(videoData); 39 const [posTimestamps, count] = this.parseFramesCount(videoData, posCount); 40 return this.parseTimestamps(videoData, posTimestamps, count); 41 } 42 43 override getTimestamp(type: TimestampType, decodedEntry: Timestamp): undefined | Timestamp { 44 if (type !== TimestampType.ELAPSED) { 45 return undefined; 46 } 47 return decodedEntry; 48 } 49 50 override processDecodedEntry( 51 index: number, 52 timestampType: TimestampType, 53 entry: Timestamp 54 ): ScreenRecordingTraceEntry { 55 const currentTimestamp = entry; 56 const initialTimestamp = this.getTimestamps(TimestampType.ELAPSED)![0]; 57 const videoTimeSeconds = 58 Number(currentTimestamp.getValueNs() - initialTimestamp.getValueNs()) / 1000000000 + 59 ParserScreenRecordingLegacy.EPSILON; 60 const videoData = this.traceFile.file; 61 return new ScreenRecordingTraceEntry(videoTimeSeconds, videoData); 62 } 63 64 private searchMagicString(videoData: Uint8Array): number { 65 let pos = ArrayUtils.searchSubarray( 66 videoData, 67 ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING 68 ); 69 if (pos === undefined) { 70 throw new TypeError("video data doesn't contain winscope magic string"); 71 } 72 pos += ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING.length; 73 return pos; 74 } 75 76 private parseFramesCount(videoData: Uint8Array, pos: number): [number, number] { 77 if (pos + 4 > videoData.length) { 78 throw new TypeError('Failed to parse frames count. Video data is too short.'); 79 } 80 const framesCount = Number(ArrayUtils.toUintLittleEndian(videoData, pos, pos + 4)); 81 pos += 4; 82 return [pos, framesCount]; 83 } 84 85 private parseTimestamps(videoData: Uint8Array, pos: number, count: number): Timestamp[] { 86 if (pos + count * 8 > videoData.length) { 87 throw new TypeError('Failed to parse timestamps. Video data is too short.'); 88 } 89 const timestamps: Timestamp[] = []; 90 for (let i = 0; i < count; ++i) { 91 const value = ArrayUtils.toUintLittleEndian(videoData, pos, pos + 8) * 1000n; 92 pos += 8; 93 timestamps.push(new Timestamp(TimestampType.ELAPSED, value)); 94 } 95 return timestamps; 96 } 97 98 private static readonly MPEG4_MAGIC_NMBER = [ 99 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32, 100 ]; // ....ftypmp42 101 private static readonly WINSCOPE_META_MAGIC_STRING = [ 102 0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 0x4d, 0x45, 0x21, 0x23, 103 ]; // #VV1NSC0PET1ME!# 104 private static readonly EPSILON = 0.00001; 105} 106 107export {ParserScreenRecordingLegacy}; 108