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 {Timestamp} from 'common/time'; 19import {AbstractParser} from 'parsers/legacy/abstract_parser'; 20import {MediaBasedTraceEntry} from 'trace/media_based_trace_entry'; 21import {ScreenRecordingUtils} from 'trace/screen_recording_utils'; 22import {TraceType} from 'trace/trace_type'; 23 24class ParserScreenRecordingLegacy extends AbstractParser { 25 override getTraceType(): TraceType { 26 return TraceType.SCREEN_RECORDING; 27 } 28 29 override getMagicNumber(): number[] { 30 return ParserScreenRecordingLegacy.MPEG4_MAGIC_NUMBER; 31 } 32 33 override getRealToMonotonicTimeOffsetNs(): bigint | undefined { 34 return undefined; 35 } 36 37 override getRealToBootTimeOffsetNs(): bigint | undefined { 38 return undefined; 39 } 40 41 override decodeTrace(videoData: Uint8Array): Array<bigint> { 42 const posCount = this.searchMagicString(videoData); 43 const [posTimestamps, count] = this.parseFramesCount(videoData, posCount); 44 return this.parseVideoData(videoData, posTimestamps, count); 45 } 46 47 protected override getTimestamp(decodedEntry: bigint): Timestamp { 48 return this.timestampConverter.makeTimestampFromMonotonicNs(decodedEntry); 49 } 50 51 override processDecodedEntry( 52 index: number, 53 entry: bigint, 54 ): MediaBasedTraceEntry { 55 const videoTimeSeconds = ScreenRecordingUtils.timestampToVideoTimeSeconds( 56 this.decodedEntries[0], 57 entry, 58 ); 59 const videoData = this.traceFile.file; 60 return new MediaBasedTraceEntry(videoTimeSeconds, videoData); 61 } 62 63 private searchMagicString(videoData: Uint8Array): number { 64 let pos = ArrayUtils.searchSubarray( 65 videoData, 66 ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING, 67 ); 68 if (pos === undefined) { 69 throw new TypeError("video data doesn't contain winscope magic string"); 70 } 71 pos += ParserScreenRecordingLegacy.WINSCOPE_META_MAGIC_STRING.length; 72 return pos; 73 } 74 75 private parseFramesCount( 76 videoData: Uint8Array, 77 pos: number, 78 ): [number, number] { 79 if (pos + 4 > videoData.length) { 80 throw new TypeError( 81 'Failed to parse frames count. Video data is too short.', 82 ); 83 } 84 const framesCount = Number( 85 ArrayUtils.toUintLittleEndian(videoData, pos, pos + 4), 86 ); 87 pos += 4; 88 return [pos, framesCount]; 89 } 90 91 private parseVideoData( 92 videoData: Uint8Array, 93 pos: number, 94 count: number, 95 ): Array<bigint> { 96 if (pos + count * 8 > videoData.length) { 97 throw new TypeError( 98 'Failed to parse timestamps. Video data is too short.', 99 ); 100 } 101 const timestamps: Array<bigint> = []; 102 for (let i = 0; i < count; ++i) { 103 const value = 104 ArrayUtils.toUintLittleEndian(videoData, pos, pos + 8) * 1000n; 105 pos += 8; 106 timestamps.push(value); 107 } 108 return timestamps; 109 } 110 111 private static readonly MPEG4_MAGIC_NUMBER = [ 112 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32, 113 ]; // ....ftypmp42 114 private static readonly WINSCOPE_META_MAGIC_STRING = [ 115 0x23, 0x56, 0x56, 0x31, 0x4e, 0x53, 0x43, 0x30, 0x50, 0x45, 0x54, 0x31, 116 0x4d, 0x45, 0x21, 0x23, 117 ]; // #VV1NSC0PET1ME!# 118 private static readonly EPSILON = 0.00001; 119} 120 121export {ParserScreenRecordingLegacy}; 122