// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { fromMicros } from "@foxglove/rostime";
import { estimateObjectSize } from "@foxglove/studio-base/players/messageMemoryEstimation.js";
import { ensureWebCodecsSupported } from "../../../utils/compatibility.js";
import { parseMessage } from "../../utils.js";
function bgrxToI420(bgrx, width, height) {
    if ((width & 1) !== 0 || (height & 1) !== 0) {
        throw new Error("I420 requires even width and height.");
    }
    const ySize = width * height;
    const uvSize = (width * height) >> 2;
    const out = new Uint8Array(ySize + uvSize + uvSize);
    const Y = out.subarray(0, ySize);
    const U = out.subarray(ySize, ySize + uvSize);
    const V = out.subarray(ySize + uvSize);
    const stride = width * 4;
    const clamp8 = (x) => (x < 0 ? 0 : x > 255 ? 255 : x | 0);
    for (let y = 0; y < height; y += 2) {
        const row0 = y * stride;
        const row1 = (y + 1) * stride;
        for (let x = 0; x < width; x += 2) {
            const p00 = row0 + x * 4;
            const p01 = row0 + (x + 1) * 4;
            const p10 = row1 + x * 4;
            const p11 = row1 + (x + 1) * 4;
            const B00 = bgrx[p00 + 0], G00 = bgrx[p00 + 1], R00 = bgrx[p00 + 2];
            const B01 = bgrx[p01 + 0], G01 = bgrx[p01 + 1], R01 = bgrx[p01 + 2];
            const B10 = bgrx[p10 + 0], G10 = bgrx[p10 + 1], R10 = bgrx[p10 + 2];
            const B11 = bgrx[p11 + 0], G11 = bgrx[p11 + 1], R11 = bgrx[p11 + 2];
            const Y00 = (77 * R00 + 150 * G00 + 29 * B00 + 128) >> 8;
            const Y01 = (77 * R01 + 150 * G01 + 29 * B01 + 128) >> 8;
            const Y10 = (77 * R10 + 150 * G10 + 29 * B10 + 128) >> 8;
            const Y11 = (77 * R11 + 150 * G11 + 29 * B11 + 128) >> 8;
            const yRow0 = (y * width) + x;
            const yRow1 = ((y + 1) * width) + x;
            Y[yRow0 + 0] = Y00;
            Y[yRow0 + 1] = Y01;
            Y[yRow1 + 0] = Y10;
            Y[yRow1 + 1] = Y11;
            const Ravg = (R00 + R01 + R10 + R11) >> 2;
            const Gavg = (G00 + G01 + G10 + G11) >> 2;
            const Bavg = (B00 + B01 + B10 + B11) >> 2;
            const Uval = clamp8(((-43 * Ravg - 85 * Gavg + 128 * Bavg) >> 8) + 128);
            const Vval = clamp8(((128 * Ravg - 107 * Gavg - 21 * Bavg) >> 8) + 128);
            const uvCol = x >> 1;
            const uvRow = y >> 1;
            const uvW = width >> 1;
            const uvPos = uvRow * uvW + uvCol;
            U[uvPos] = Uval;
            V[uvPos] = Vval;
        }
    }
    return out;
}
function convertNv12ToI420(nv12Buffer, width, height) {
    const ySize = width * height;
    const uvSize = ySize / 4;
    const i420Buffer = new Uint8Array(ySize + 2 * uvSize);
    // Copy Y plane (it's the same in both formats)
    i420Buffer.set(nv12Buffer.subarray(0, ySize));
    const uPlaneOffset = ySize;
    const vPlaneOffset = ySize + uvSize;
    // De-interleave UV plane
    for (let i = 0; i < uvSize; i++) {
        const uvIndex = ySize + i * 2;
        i420Buffer[uPlaneOffset + i] = nv12Buffer[uvIndex] ?? 0; // U
        i420Buffer[vPlaneOffset + i] = nv12Buffer[uvIndex + 1] ?? 0; // V
    }
    return i420Buffer;
}
function createVideoDecoder({ topic, callback, topicDecoders, }) {
    return new VideoDecoder({
        output: async (frame) => {
            let finalBuffer = new Uint8Array(frame.allocationSize());
            if (frame.format === "I420") {
                finalBuffer = new Uint8Array(frame.allocationSize());
                await frame.copyTo(finalBuffer);
            }
            else if (frame.format === "NV12") {
                const nv12Buffer = new Uint8Array(frame.allocationSize());
                await frame.copyTo(nv12Buffer);
                finalBuffer = convertNv12ToI420(nv12Buffer, frame.codedWidth, frame.codedHeight);
            }
            else if (frame.format === "RGBX") {
                const bgrxBuffer = new Uint8Array(frame.allocationSize());
                await frame.copyTo(bgrxBuffer);
                console.warn("PointCloud decoder received RGBX frame.");
                finalBuffer = bgrxToI420(bgrxBuffer, frame.codedWidth, frame.codedHeight);
            }
            if (frame.format !== 'I420' && frame.format !== 'NV12' && frame.format !== 'RGBX') {
                console.warn("PointCloud decoder received unexpected frame format:", frame.format);
            }
            const receiveTime = topicDecoders.get(topic)?.timing.get(frame.timestamp)
                ??
                    fromMicros(frame.timestamp);
            const foxgloveMessage = {
                timestamp: receiveTime,
                frame_id: `h264-${topic}-frame`,
                width: frame.displayWidth,
                height: frame.displayHeight,
                data: finalBuffer,
                encoding: "yuv420p",
                step: frame.displayWidth
            };
            callback({
                topic,
                receiveTime,
                message: foxgloveMessage,
                sizeInBytes: estimateObjectSize(foxgloveMessage),
                schemaName: "foxglove.RawImage"
            });
            frame.close();
        },
        error: console.error
    });
}
export async function deserializeEncodedFrameH264({ topic, message, topicDecoders, callback }) {
    const { receiveTime } = parseMessage(message);
    const supported = ensureWebCodecsSupported("VideoDecoder");
    if (!supported) {
        return;
    }
    if (!topicDecoders.has(topic)) {
        const decoder = createVideoDecoder({ topic, callback, topicDecoders });
        decoder.configure({
            codec: "avc1.42001E",
            optimizeForLatency: true
        });
        topicDecoders.set(topic, {
            decoder,
            timing: new Map()
        });
    }
    const decoderInfo = topicDecoders.get(topic);
    if (!decoderInfo) {
        return;
    }
    if (decoderInfo.decoder.decodeQueueSize > 60) {
        decoderInfo.decoder.reset();
        return;
    }
    const microTimestamp = receiveTime.sec * 1_000_000 + Math.floor(receiveTime.nsec / 1_000);
    decoderInfo.timing.set(microTimestamp, receiveTime);
    const frame = {
        type: 'key',
        data: message.data,
        timestamp: microTimestamp
    };
    try {
        const chunk = new EncodedVideoChunk(frame);
        decoderInfo.decoder.decode(chunk);
    }
    catch {
        topicDecoders.delete(topic);
    }
}
//# sourceMappingURL=h264.js.map