Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit c2fa194d authored by Simon Chan's avatar Simon Chan
Browse files

feat(scrcpy): parse and return h264 configurations

for use in remuxing/recoding
parent 7c202c1b
Loading
Loading
Loading
Loading
+191 −155
Original line number Diff line number Diff line
// cspell: ignore golomb
// cspell: ignore qpprime

// H.264 has two standards: ITU-T H.264 and ISO/IEC 14496-10
// they have the same content, and refer themselves as "H.264".
// The name "AVC" (Advanced Video Coding) is only used in ISO spec name,
// and other ISO specs referring to H.264.
// Because this module parses H.264 Annex B format,
// it's named "h264" instead of "avc".

class BitReader {
    private buffer: Uint8Array;

@@ -43,6 +50,9 @@ class BitReader {
    }
}

/**
 * Parse NAL units from H.264 Annex B formatted data.
 */
function* iterateNalu(buffer: Uint8Array): Generator<Uint8Array> {
    // -1 means we haven't found the first start code
    let start = -1;
@@ -126,7 +136,7 @@ function* iterateNalu(buffer: Uint8Array): Generator<Uint8Array> {
                inEmulation = true;
                break;
            default:
                // `0x000004` or larger are ok
                // `0x000004` or larger are as-is
                break;
        }
    }
@@ -139,9 +149,9 @@ function* iterateNalu(buffer: Uint8Array): Generator<Uint8Array> {
}

// 7.3.2.1.1 Sequence parameter set data syntax
export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
    for (const nalu of iterateNalu(new Uint8Array(buffer))) {
        const reader = new BitReader(nalu);
// Variable names in this method uses the snake_case convention as in the spec for easier referencing.
export function parseSequenceParameterSet(buffer: Uint8Array) {
    const reader = new BitReader(buffer);
    if (reader.next() !== 0) {
        throw new Error("Invalid data");
    }
@@ -150,7 +160,7 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
    const nal_unit_type = reader.read(5);

    if (nal_unit_type !== 7) {
            continue;
        throw new Error("Invalid data");
    }

    if (nal_ref_idc === 0) {
@@ -209,11 +219,7 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
        const seq_scaling_matrix_present_flag = !!reader.next();
        if (seq_scaling_matrix_present_flag) {
            const seq_scaling_list_present_flag: boolean[] = [];
                for (
                    let i = 0;
                    i < (chroma_format_idc !== 3 ? 8 : 12);
                    i += 1
                ) {
            for (let i = 0; i < (chroma_format_idc !== 3 ? 8 : 12); i += 1) {
                seq_scaling_list_present_flag[i] = !!reader.next();
                if (seq_scaling_list_present_flag[i])
                    if (i < 6) {
@@ -246,8 +252,7 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
            reader.decodeExponentialGolombNumber();
        const offset_for_ref_frame: number[] = [];
        for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i += 1) {
                offset_for_ref_frame[i] =
                    reader.decodeExponentialGolombNumber();
            offset_for_ref_frame[i] = reader.decodeExponentialGolombNumber();
        }
    }

@@ -313,9 +318,40 @@ export function parse_sequence_parameter_set(buffer: ArrayBuffer) {
    };
}

    throw new Error("Invalid data");
/**
 * Find Sequence Parameter Set (SPS) and Picture Parameter Set (PPS)
 * from H.264 Annex B formatted data.
 */
export function parseH264Configuration(buffer: Uint8Array) {
    let sequenceParameterSet: Uint8Array | undefined;
    let pictureParameterSet: Uint8Array | undefined;

    for (const nalu of iterateNalu(buffer)) {
        const naluType = nalu[0]! & 0x1f;
        switch (naluType) {
            case 7: // Sequence parameter set
                sequenceParameterSet = nalu;
                if (pictureParameterSet) {
                    return {
                        sequenceParameterSet,
                        pictureParameterSet,
                    };
                }
                break;
            case 8: // Picture parameter set
                pictureParameterSet = nalu;
                if (sequenceParameterSet) {
                    return {
                        sequenceParameterSet,
                        pictureParameterSet,
                    };
                }
                break;
            default:
                // ignore
                break;
        }
    }

export type SequenceParameterSet = ReturnType<
    typeof parse_sequence_parameter_set
>;
    throw new Error("Invalid data");
}
+3 −3
Original line number Diff line number Diff line
export * from './codec-options.js';
export * from './options.js';
export * from "./codec-options.js";
export * from "./h264-configuration.js";
export * from "./options.js";
export * from "./scroll.js";
export * from './sps.js';
+14 −8
Original line number Diff line number Diff line
@@ -16,11 +16,14 @@ import {
} from "../types.js";

import { CodecOptions } from "./codec-options.js";
import {
    parseH264Configuration,
    parseSequenceParameterSet,
} from "./h264-configuration.js";
import {
    ScrcpyScrollController1_16,
    type ScrcpyScrollController,
} from "./scroll.js";
import { parse_sequence_parameter_set } from "./sps.js";

export enum ScrcpyLogLevel {
    Verbose = "verbose",
@@ -109,8 +112,9 @@ export const ScrcpyBackOrScreenOnControlMessage1_16 = new Struct().uint8(
    ScrcpyControlMessageType.BackOrScreenOn as const
);

export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16>
    implements ScrcpyOptions<T>
export class ScrcpyOptions1_16<
    T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16
> implements ScrcpyOptions<T>
{
    public value: Partial<T>;

@@ -206,10 +210,10 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
                new TransformStream({
                    transform(packet, controller) {
                        if (packet.pts === NO_PTS) {
                            const sequenceParameterSet =
                                parse_sequence_parameter_set(
                                    packet.data.slice().buffer
                                );
                            const {
                                sequenceParameterSet,
                                pictureParameterSet,
                            } = parseH264Configuration(packet.data.slice());

                            const {
                                profile_idc: profileIndex,
@@ -222,7 +226,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
                                frame_crop_right_offset,
                                frame_crop_top_offset,
                                frame_crop_bottom_offset,
                            } = sequenceParameterSet;
                            } = parseSequenceParameterSet(sequenceParameterSet);

                            const encodedWidth =
                                (pic_width_in_mbs_minus1 + 1) * 16;
@@ -243,6 +247,8 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
                            header = packet.data;
                            controller.enqueue({
                                type: "configuration",
                                pictureParameterSet,
                                sequenceParameterSet,
                                data: {
                                    profileIndex,
                                    constraintSet,
+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ export interface H264Configuration {

export interface ScrcpyVideoStreamConfigurationPacket {
    type: "configuration";
    sequenceParameterSet: Uint8Array;
    pictureParameterSet: Uint8Array;
    data: H264Configuration;
}