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