軟編碼Flv 到Mp4 容器(三) flv metadata tag解析

https://github.com/332065255/flv2fmp4

代碼庫


軟編碼Flv 到Mp4 容器(一)
軟編碼Flv 到Mp4 容器(二) flv tag拆解
軟編碼Flv 到Mp4 容器(三) flv metadata tag解析
軟編碼Flv 到Mp4 容器(四) fmp4 總覽和基礎講解
軟編碼Flv 到Mp4 容器(五) fmp4 ftyp box 和moov>mvhd box詳解
軟編碼Flv 到Mp4 容器(六) fmp4 moov>trak>tkhd box 和 moov>trak>mdia>mdhd box講解
軟編碼Flv 到Mp4 容器(七) fmp4 mdia>hdlr box 和 mdia>minf> smhd 和dinf box講解
軟編碼Flv 到Mp4 容器(八) fmp4 mdia>stbl>stsd box 講解
軟編碼Flv 到Mp4 容器(九) fmp4 stts stsc stsz stco box 講解
軟編碼Flv 到Mp4 容器(十) fmp4 mvex box 講解
軟編碼Flv 到Mp4 容器(十一) fmp4 moof box詳解
軟編碼Flv 到Mp4 容器(十二) fmp4 mdat box詳解
軟編碼Flv 到Mp4 容器(十三) fmp4 生成ftyp和moov所必要的 flv數據


metadata的內容相對來說比較複雜
http://blog.csdn.net/zengraoli/article/details/7742278 這篇博客中講了一部分
metadata的內容大致如下

  • Name:onMetaData
  • Value:Array數組
    • key:鍵
    • value:普通類型值
    • key:鍵
    • value:普通類型值
    • key:鍵
    • value:數組類型值
      • key:鍵
      • value:普通類型值

一般情況,只有兩層數組

0 = Number
1 = Boolean
2 = String
3 = Object
4 = MovieClip (reserved, not supported)
5 = Null
6 = Undefined
7 = Reference
8 = ECMA array
9 = Object end marker
10 = Strict array
11 = Date
12 = Long string

metadata body的第一個字節,通常是0x02,所以是string類型,
第2-3個字節爲長度,通常爲10,後面讀10個字節,轉換charcode,即爲onmetadata,
繼續下一個字節是0x08,是數組類型,略過4個字節,這是數組長度,
下一個字節理論上也是0x02,讀2個字節的字符長度,再讀字符長度的uint8,就是字段名
後面的字節就是value值得類型,依次這麼往下讀

詳細代碼在flvdemux.js
代碼借鑑了flv.js的代碼

let le = (function() {
    let buf = new ArrayBuffer(2);
    (new DataView(buf)).setInt16(0, 256, true); // little-endian write
    return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE
})();
export default class flvDemux {

    constructor() {

    }
    static parseObject(arrayBuffer, dataOffset, dataSize) {

        let name = flvDemux.parseString(arrayBuffer, dataOffset, dataSize);
        let value = flvDemux.parseScript(arrayBuffer, dataOffset + name.size);
        let isObjectEnd = value.objectEnd;

        return {
            data: {
                name: name.data,
                value: value.data
            },
            size: value.size,
            objectEnd: isObjectEnd
        };
    }

    static parseVariable(arrayBuffer, dataOffset, dataSize) {
        return flvDemux.parseObject(arrayBuffer, dataOffset, dataSize);
    }
    static parseLongString(arrayBuffer, dataOffset, dataSize) {
        if (dataSize < 4) {
            throw new IllegalStateException('Data not enough when parse LongString');
        }
        let v = new DataView(arrayBuffer, dataOffset);
        let length = v.getUint32(0, !le);

        let str;
        if (length > 0) {
            str = decodeUTF8(new Uint8Array(arrayBuffer, dataOffset + 4, length));
        } else {
            str = '';
        }

        return {
            data: str,
            size: 4 + length
        };
    }
    static parseDate(arrayBuffer, dataOffset, dataSize) {
        if (dataSize < 10) {
            throw new IllegalStateException('Data size invalid when parse Date');
        }
        let v = new DataView(arrayBuffer, dataOffset);
        let timestamp = v.getFloat64(0, !le);
        let localTimeOffset = v.getInt16(8, !le);
        timestamp += localTimeOffset * 60 * 1000; // get UTC time

        return {
            data: new Date(timestamp),
            size: 8 + 2
        };
    }
    static parseString(arrayBuffer, dataOffset, dataSize) {
        let v = new DataView(arrayBuffer, dataOffset);
        let length = v.getUint16(0, !le);
        let str;
        if (length > 0) {
            str = decodeUTF8(new Uint8Array(arrayBuffer, dataOffset + 2, length));
        } else {
            str = '';
        }
        return {
            data: str,
            size: 2 + length
        };
    }

    /**
     * 解析metadata
     */
    static parseMetadata(arr) {
        let name = flvDemux.parseScript(arr, 0);
        let value = flvDemux.parseScript(arr, name.size, arr.length - name.size);

        console.log(value);
    }
    static parseScript(arr, offset, dataSize) {
        let dataOffset = offset;
        let object = {};
        let uint8 = new Uint8Array(arr);
        let buffer = uint8.buffer;
        let dv = new DataView(buffer, 0, dataSize);
        let value = null;

        let type = (dv.getUint8(dataOffset));
        dataOffset += 1;
        switch (type) {
            case 0: // Number(Double) type
                value = dv.getFloat64(dataOffset, !le);
                dataOffset += 8;
                break;
            case 1:
                { // Boolean type
                    let b = dv.getUint8(dataOffset);
                    value = b ? true : false;
                    dataOffset += 1;
                    break;
                }
            case 2:
                { // String type
                    // dataOffset += 1;
                    let amfstr = flvDemux.parseString(buffer, dataOffset);
                    value = amfstr.data;
                    dataOffset += amfstr.size;
                    break;
                }
            case 3:

                { // Object(s) type
                    value = {};
                    let terminal = 0; // workaround for malformed Objects which has missing ScriptDataObjectEnd
                    if ((dv.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) {
                        terminal = 3;
                    }
                    while (offset < dataSize - 4) { // 4 === type(UI8) + ScriptDataObjectEnd(UI24)
                        let amfobj = flvDemux.parseObject(buffer, dataOffset, dataSize - offset - terminal);

                        if (amfobj.objectEnd)
                            break;
                        value[amfobj.data.name] = amfobj.data.value;
                        dataOffset += amfobj.size;
                    }
                    if (offset <= dataSize - 3) {
                        let marker = v.getUint32(dataOffset - 1, !le) & 0x00FFFFFF;
                        if (marker === 9) {
                            dataOffset += 3;
                        }
                    }
                    break;
                }
            case 8:
                { // ECMA array type (Mixed array)
                    value = {};
                    // dataOffset += 1;
                    dataOffset += 4; // ECMAArrayLength(UI32)
                    let terminal = 0; // workaround for malformed MixedArrays which has missing ScriptDataObjectEnd
                    if ((dv.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) {
                        terminal = 3;
                    }
                    while (dataOffset < dataSize - 8) { // 8 === type(UI8) + ECMAArrayLength(UI32) + ScriptDataVariableEnd(UI24)
                        let amfvar = flvDemux.parseVariable(buffer, dataOffset);

                        if (amfvar.objectEnd)
                            break;
                        value[amfvar.data.name] = amfvar.data.value;
                        dataOffset = amfvar.size;
                    }
                    if (dataOffset <= dataSize - 3) {
                        let marker = dv.getUint32(dataOffset - 1, !le) & 0x00FFFFFF;
                        if (marker === 9) {
                            dataOffset += 3;
                        }
                    }
                    break;
                }
            case 9: // ScriptDataObjectEnd
                value = undefined;
                dataOffset = 1;
                objectEnd = true;
                break;
            case 10:
                { // Strict array type
                    // ScriptDataValue[n]. NOTE: according to video_file_format_spec_v10_1.pdf
                    value = [];
                    let strictArrayLength = dv.getUint32(dataOffset, !le);
                    dataOffset += 4;
                    for (let i = 0; i < strictArrayLength; i++) {
                        let val = flvDemux.parseScript(buffer, dataOffset);
                        value.push(val.data);
                        dataOffset = val.size;
                    }
                    break;
                }
            case 11:
                { // Date type
                    let date = flvDemux.parseDate(buffer, dataOffset + 1, dataSize - 1);
                    value = date.data;
                    dataOffset += date.size;
                    break;
                }
            case 12:
                { // Long string type
                    let amfLongStr = flvDemux.parseString(buffer, dataOffset + 1, dataSize - 1);
                    value = amfLongStr.data;
                    dataOffset += amfLongStr.size;
                    break;
                }
            default:
                // ignore and skip
                dataOffset = dataSize;
                console.log('AMF', 'Unsupported AMF value type ' + type);
        }
        return {
            data: value,
            size: dataOffset,
        };
    }
}
發佈了25 篇原創文章 · 獲贊 14 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章