ส่วนขยายแหล่งที่มาของสื่อสำหรับเสียง

Dale Curtis
Dale Curtis

บทนำ

Media Source Extensions (MSE) เพิ่มการควบคุมการเล่นและบัฟเฟอร์สำหรับองค์ประกอบ <audio> และ <video> ของ HTML5 แม้ว่าเดิมทีจะพัฒนาขึ้นเพื่ออำนวยความสะดวกให้กับโปรแกรมเล่นวิดีโอที่ใช้ Dynamic Adaptive Streaming over HTTP (DASH) แต่ด้านล่างนี้เราจะดูวิธีใช้สำหรับเสียง โดยเฉพาะสำหรับการเล่นแบบต่อเนื่อง

คุณอาจเคยฟังอัลบั้มเพลงที่เพลงแต่ละแทร็กเชื่อมต่อกันได้อย่างราบรื่น หรืออาจกำลังฟังอยู่ตอนนี้ ศิลปินสร้างประสบการณ์การเล่นแบบไม่มีช่วงพักเหล่านี้ทั้งเพื่อแสดงถึงรสนิยมทางศิลปะและเพื่อคงไว้ซึ่งลักษณะของแผ่นเสียงไวนิลและซีดีที่บันทึกเสียงเป็นไฟล์ต่อเนื่อง แต่ในปัจจุบัน ประสบการณ์การฟังเสียงที่ราบรื่นนี้มักหายไปเนื่องจากการทำงานของตัวแปลงรหัสเสียงสมัยใหม่อย่าง MP3 และ AAC

เราจะอธิบายรายละเอียดของเหตุผลด้านล่าง แต่ตอนนี้มาเริ่มด้วยการสาธิตกันก่อน ด้านล่างนี้คือช่วง 30 วินาทีแรกของ Sintel ที่ยอดเยี่ยมซึ่งถูกตัดออกเป็นไฟล์ MP3 แยกกัน 5 ไฟล์และประกอบเข้าด้วยกันอีกครั้งโดยใช้ MSE เส้นสีแดงแสดงช่องว่างที่เกิดขึ้นระหว่างการสร้าง (การเข้ารหัส) ไฟล์ MP3 แต่ละไฟล์ คุณอาจได้ยินเสียงขัดข้องในจุดเหล่านี้

การสาธิต

อี๋ ประสบการณ์นี้ไม่ดีเลย เราทำได้ดีกว่านี้ หากต้องการทำงานเพิ่มอีกเล็กน้อยโดยใช้ไฟล์ MP3 เดียวกันกับในตัวอย่างด้านบน เราสามารถใช้ MSE เพื่อนำช่วงพักที่น่ารำคาญเหล่านั้นออกได้ เส้นสีเขียวในการแสดงตัวอย่างถัดไปจะระบุตำแหน่งที่ไฟล์เชื่อมต่อกันและนำช่องว่างออก ซึ่งจะเล่นได้อย่างราบรื่นใน Chrome 38 ขึ้นไป

การสาธิต

การสร้างเนื้อหาแบบไม่มีช่วงพักมีหลากหลายวิธี ในการสาธิตนี้ เราจะมุ่งเน้นที่ประเภทไฟล์ที่ผู้ใช้ทั่วไปอาจมี โดยแต่ละไฟล์ได้รับการเข้ารหัสแยกกันโดยไม่คำนึงถึงส่วนของเสียงก่อนหรือหลัง

การตั้งค่าพื้นฐาน

ก่อนอื่น เรามาย้อนกลับไปดูการตั้งค่าพื้นฐานของอินสแตนซ์ MediaSource ส่วนขยายแหล่งที่มาของสื่อเป็นเพียงส่วนขยายขององค์ประกอบสื่อที่มีอยู่ ด้านล่างนี้ เรากําลังกําหนด Object URL ซึ่งแสดงอินสแตนซ์ MediaSource ให้กับแอตทริบิวต์แหล่งที่มาขององค์ประกอบเสียง เช่นเดียวกับที่คุณกําหนด URL มาตรฐาน

var audio = document.createElement('audio');
var mediaSource = new MediaSource();
var SEGMENTS = 5;

mediaSource.addEventListener('sourceopen', function() {
    var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');

    function onAudioLoaded(data, index) {
    // Append the ArrayBuffer data into our new SourceBuffer.
    sourceBuffer.appendBuffer(data);
    }

    // Retrieve an audio segment via XHR.  For simplicity, we're retrieving the
    // entire segment at once, but we could also retrieve it in chunks and append
    // each chunk separately.  MSE will take care of assembling the pieces.
    GET('sintel/sintel_0.mp3', function(data) { onAudioLoaded(data, 0); } );
});

audio.src = URL.createObjectURL(mediaSource);

เมื่อเชื่อมต่อออบเจ็กต์ MediaSource แล้ว ระบบจะทำการเริ่มต้นบางอย่างและเรียกเหตุการณ์ sourceopen ออกมาในท้ายที่สุด เมื่อถึงจุดนั้นเราจะสร้าง SourceBuffer ได้ ในตัวอย่างข้างต้น เรากำลังสร้าง audio/mpeg ซึ่งสามารถแยกวิเคราะห์และถอดรหัสกลุ่ม MP3 ได้ นอกจากนี้ยังมีประเภทอื่นๆ อีกหลายประเภท

รูปแบบคลื่นที่ผิดปกติ

เราจะกลับมาดูโค้ดในอีกสักครู่ แต่ตอนนี้มาพิจารณาไฟล์ที่เราเพิ่งเพิ่มต่อท้ายกันโดยละเอียด โดยเฉพาะส่วนท้าย ด้านล่างคือกราฟของตัวอย่าง 3, 000 รายการล่าสุดโดยเฉลี่ยจากทั้ง 2 ช่องของแทร็ก sintel_0.mp3 พิกเซลแต่ละพิกเซลบนเส้นสีแดงคือตัวอย่างทศนิยมแบบลอยในช่วง [-1.0, 1.0]

End of sintel_0.mp3

ตัวอย่างที่เป็น 0 (ไม่มีเสียง) จำนวนมากนั้นหมายความว่าอย่างไร จริงๆ แล้วจุดเหล่านี้เกิดจากข้อบกพร่องในการบีบอัดที่เกิดขึ้นระหว่างการเข้ารหัส โปรแกรมเปลี่ยนไฟล์เกือบทุกโปรแกรมจะมีการเพิ่มการเติมข้อมูลบางประเภท ในกรณีนี้ LAME ได้เพิ่มตัวอย่างการถอดเสียง 576 รายการไว้ที่ส่วนท้ายของไฟล์

นอกจากการเติมที่ส่วนท้ายแล้ว ไฟล์แต่ละไฟล์ยังมีการเติมที่ส่วนต้นด้วย หากดูแทร็ก sintel_1.mp3 ล่วงหน้า เราจะเห็นการถ่วงเวลาอีก 576 ตัวอย่างอยู่ด้านหน้า จำนวนการเติมจะแตกต่างกันไปตามโปรแกรมเปลี่ยนไฟล์และเนื้อหา แต่เราทราบค่าที่แน่นอนโดยอิงตาม metadata ที่รวมอยู่ในไฟล์แต่ละไฟล์

ต้นของ sintel_1.mp3

ต้นของ sintel_1.mp3

ส่วนที่เป็นเสียงเงียบที่จุดเริ่มต้นและจุดสิ้นสุดของแต่ละไฟล์คือสาเหตุที่ทำให้เกิดข้อบกพร่องระหว่างกลุ่มในเดโมก่อนหน้า เราต้องนำช่วงเงียบเหล่านี้ออกเพื่อให้เล่นเพลงแบบต่อเนื่อง โชคดีที่การดำเนินการนี้ทำได้ง่ายด้วย MediaSource ด้านล่างนี้ เราจะแก้ไขเมธอด onAudioLoaded() เพื่อใช้กรอบเวลาต่อท้ายและการเลื่อนเวลาเริ่มต้นเพื่อนำช่วงเงียบนี้ออก

โค้ดตัวอย่าง

function onAudioLoaded(data, index) {
    // Parsing gapless metadata is unfortunately non trivial and a bit messy, so
    // we'll glaze over it here; see the appendix for details.
    // ParseGaplessData() will return a dictionary with two elements:
    //
    //    audioDuration: Duration in seconds of all non-padding audio.
    //    frontPaddingDuration: Duration in seconds of the front padding.
    //
    var gaplessMetadata = ParseGaplessData(data);

    // Each appended segment must be appended relative to the next.  To avoid any
    // overlaps, we'll use the end timestamp of the last append as the starting
    // point for our next append or zero if we haven't appended anything yet.
    var appendTime = index > 0 ? sourceBuffer.buffered.end(0) : 0;

    // Simply put, an append window allows you to trim off audio (or video) frames
    // which fall outside of a specified time range.  Here, we'll use the end of
    // our last append as the start of our append window and the end of the real
    // audio data for this segment as the end of our append window.
    sourceBuffer.appendWindowStart = appendTime;
    sourceBuffer.appendWindowEnd = appendTime + gaplessMetadata.audioDuration;

    // The timestampOffset field essentially tells MediaSource where in the media
    // timeline the data given to appendBuffer() should be placed.  I.e., if the
    // timestampOffset is 1 second, the appended data will start 1 second into
    // playback.
    //
    // MediaSource requires that the media timeline starts from time zero, so we
    // need to ensure that the data left after filtering by the append window
    // starts at time zero.  We'll do this by shifting all of the padding we want
    // to discard before our append time (and thus, before our append window).
    sourceBuffer.timestampOffset =
        appendTime - gaplessMetadata.frontPaddingDuration;

    // When appendBuffer() completes, it will fire an updateend event signaling
    // that it's okay to append another segment of media.  Here, we'll chain the
    // append for the next segment to the completion of our current append.
    if (index == 0) {
    sourceBuffer.addEventListener('updateend', function() {
        if (++index < SEGMENTS) {
        GET('sintel/sintel_' + index + '.mp3',
            function(data) { onAudioLoaded(data, index); });
        } else {
        // We've loaded all available segments, so tell MediaSource there are no
        // more buffers which will be appended.
        mediaSource.endOfStream();
        URL.revokeObjectURL(audio.src);
        }
    });
    }

    // appendBuffer() will now use the timestamp offset and append window settings
    // to filter and timestamp the data we're appending.
    //
    // Note: While this demo uses very little memory, more complex use cases need
    // to be careful about memory usage or garbage collection may remove ranges of
    // media in unexpected places.
    sourceBuffer.appendBuffer(data);
}

รูปแบบคลื่นที่ราบรื่น

มาดูกันว่าโค้ดใหม่สุดเจ๋งของเราทำงานได้ดีแค่ไหนด้วยการดูรูปคลื่นอีกครั้งหลังจากที่เราใช้กรอบเวลาต่อท้าย ด้านล่างนี้ คุณจะเห็นได้ว่าเราได้นำส่วนที่ไม่มีเสียงที่ส่วนท้ายของ sintel_0.mp3 (สีแดง) และส่วนที่ไม่มีเสียงที่ส่วนต้นของ sintel_1.mp3 (สีน้ำเงิน) ออกแล้ว เพื่อให้ช่วงต่างๆ ตัดต่อกันได้อย่างราบรื่น

การรวม sintel_0.mp3 กับ sintel_1.mp3

บทสรุป

เท่านี้เราก็ได้ต่อวิดีโอทั้ง 5 คลิปเข้าด้วยกันอย่างราบรื่นแล้ว และก็มาถึงช่วงท้ายของการสาธิต ก่อนจากกัน คุณอาจสังเกตเห็นว่าเมธอด onAudioLoaded() ของเราไม่ได้คำนึงถึงคอนเทนเนอร์หรือตัวแปลงรหัส ซึ่งหมายความว่าเทคนิคเหล่านี้ทั้งหมดจะใช้งานได้โดยไม่คำนึงถึงประเภทคอนเทนเนอร์หรือโค้ดรูปแบบ ด้านล่างนี้คุณสามารถเล่นไฟล์ MP4 แบบแยกส่วนต้นฉบับที่พร้อมใช้งาน DASH แทน MP3 ซ้ำได้

การสาธิต

หากต้องการดูข้อมูลเพิ่มเติม โปรดดูภาคผนวกด้านล่างเพื่อดูรายละเอียดการสร้างเนื้อหาแบบไม่มีช่วงพักและการแยกวิเคราะห์ข้อมูลเมตา นอกจากนี้ คุณยังดู gapless.js เพื่อดูโค้ดที่ขับเคลื่อนการสาธิตนี้อย่างละเอียดได้ด้วย

ขอขอบคุณที่อ่าน

ภาคผนวก ก. การสร้างเนื้อหาที่ต่อเนื่อง

การสร้างเนื้อหาที่ต่อเนื่องนั้นไม่ใช่เรื่องง่าย ด้านล่างนี้เราจะอธิบายการสร้างสื่อ Sintel ที่ใช้ในการสาธิตนี้ ในการเริ่มต้น คุณจะต้องมีสำเนาซาวด์แทร็ก FLAC แบบไม่สูญเสียรายละเอียดสำหรับ Sintel ด้านล่างนี้คือ SHA1 ไว้ใช้อ้างอิงในอนาคต สำหรับเครื่องมือ คุณจะต้องมี FFmpeg, MP4Box, LAME และการติดตั้ง OSX ด้วย afconvert

unzip Jan_Morgenstern-Sintel-FLAC.zip
sha1sum 1-Snow_Fight.flac
# 0535ca207ccba70d538f7324916a3f1a3d550194  1-Snow_Fight.flac

ก่อนอื่น เราจะแยก 31.5 วินาทีแรกของแทร็ก 1-Snow_Fight.flac ออก นอกจากนี้ เรายังต้องการเพิ่มการค่อยๆ เลือนออกเป็นเวลา 2.5 วินาทีโดยเริ่มที่ 28 วินาทีเพื่อหลีกเลี่ยงการคลิกเมื่อเล่นจบ เมื่อใช้บรรทัดคำสั่ง FFmpeg ด้านล่าง เราจะทำสิ่งเหล่านี้ทั้งหมดและใส่ผลลัพธ์ไว้ใน sintel.flac ได้

ffmpeg -i 1-Snow_Fight.flac -t 31.5 -af "afade=t=out:st=28:d=2.5" sintel.flac

ต่อไป เราจะแบ่งไฟล์ออกเป็นไฟล์ Wave 5 ไฟล์ขนาด 6.5 วินาทีต่อไฟล์ เนื่องจากการใช้ Wave นั้นง่ายที่สุด เนื่องจากโปรแกรมเปลี่ยนไฟล์เกือบทุกโปรแกรมรองรับการนำเข้าไฟล์ประเภทนี้ เราทําเช่นนี้ได้อย่างแม่นยําด้วย FFmpeg ซึ่งหลังจากนั้นเราจะมี sintel_0.wav, sintel_1.wav, sintel_2.wav, sintel_3.wav และ sintel_4.wav

ffmpeg -i sintel.flac -acodec pcm_f32le -map 0 -f segment \
        -segment_list out.list -segment_time 6.5 sintel_%d.wav

ต่อไปมาสร้างไฟล์ MP3 กัน LAME มีตัวเลือกหลายอย่างในการสร้างเนื้อหาแบบไม่มีช่วงพัก หากควบคุมเนื้อหาได้ คุณอาจพิจารณาใช้ --nogap กับการเข้ารหัสไฟล์ทั้งหมดพร้อมกันเพื่อหลีกเลี่ยงการใส่ค่าเพิ่มระหว่างกลุ่ม แต่สำหรับวัตถุประสงค์ในการสาธิตนี้ เราต้องการการเติม ดังนั้นเราจะใช้การเข้ารหัส VBR คุณภาพสูงมาตรฐานของไฟล์ Wave

lame -V=2 sintel_0.wav sintel_0.mp3
lame -V=2 sintel_1.wav sintel_1.mp3
lame -V=2 sintel_2.wav sintel_2.mp3
lame -V=2 sintel_3.wav sintel_3.mp3
lame -V=2 sintel_4.wav sintel_4.mp3

เพียงเท่านี้คุณก็สร้างไฟล์ MP3 ได้แล้ว ต่อไปมาดูการสร้างไฟล์ MP4 ที่กระจัดกระจายกัน เราจะทำตามวิธีการของ Apple ในการสร้างสื่อที่มาสเตอร์สำหรับ iTunes ด้านล่างนี้ เราจะแปลงไฟล์ Wave เป็นไฟล์ CAF ระดับกลางตามวิธีการ จากนั้นจึงเข้ารหัสเป็น AAC ในคอนเทนเนอร์ MP4 โดยใช้พารามิเตอร์ที่แนะนำ

afconvert sintel_0.wav sintel_0_intermediate.caf -d 0 -f caff \
            --soundcheck-generate
afconvert sintel_1.wav sintel_1_intermediate.caf -d 0 -f caff \
            --soundcheck-generate
afconvert sintel_2.wav sintel_2_intermediate.caf -d 0 -f caff \
            --soundcheck-generate
afconvert sintel_3.wav sintel_3_intermediate.caf -d 0 -f caff \
            --soundcheck-generate
afconvert sintel_4.wav sintel_4_intermediate.caf -d 0 -f caff \
            --soundcheck-generate
afconvert sintel_0_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
            -b 256000 -q 127 -s 2 sintel_0.m4a
afconvert sintel_1_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
            -b 256000 -q 127 -s 2 sintel_1.m4a
afconvert sintel_2_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
            -b 256000 -q 127 -s 2 sintel_2.m4a
afconvert sintel_3_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
            -b 256000 -q 127 -s 2 sintel_3.m4a
afconvert sintel_4_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
            -b 256000 -q 127 -s 2 sintel_4.m4a

ตอนนี้เรามีไฟล์ M4A หลายไฟล์ที่ต้องแบ่งอย่างเหมาะสมก่อนจึงจะใช้กับ MediaSource ได้ เราจะใช้ขนาดข้อมูลโค้ดที่ 1 วินาที MP4Box จะเขียน MP4 ที่แบ่งออกเป็นแต่ละส่วนเป็น sintel_#_dashinit.mp4 พร้อมกับไฟล์ Manifest ของ MPEG-DASH (sintel_#_dash.mpd) ซึ่งสามารถทิ้งได้

MP4Box -dash 1000 sintel_0.m4a && mv sintel_0_dashinit.mp4 sintel_0.mp4
MP4Box -dash 1000 sintel_1.m4a && mv sintel_1_dashinit.mp4 sintel_1.mp4
MP4Box -dash 1000 sintel_2.m4a && mv sintel_2_dashinit.mp4 sintel_2.mp4
MP4Box -dash 1000 sintel_3.m4a && mv sintel_3_dashinit.mp4 sintel_3.mp4
MP4Box -dash 1000 sintel_4.m4a && mv sintel_4_dashinit.mp4 sintel_4.mp4
rm sintel_{0,1,2,3,4}_dash.mpd

เท่านี้ก็เรียบร้อย ตอนนี้เรามีไฟล์ MP4 และ MP3 ที่มีการแบ่งส่วนพร้อมข้อมูลเมตาที่ถูกต้องซึ่งจำเป็นต่อการเล่นแบบไม่มีช่วงพัก ดูรายละเอียดเพิ่มเติมเกี่ยวกับลักษณะข้อมูลเมตาดังกล่าวได้ในภาคผนวก ข.

ภาคผนวก ข.: การแยกวิเคราะห์ข้อมูลเมตาแบบไม่มีช่วงพัก

การแยกวิเคราะห์ข้อมูลเมตาแบบไม่มีช่วงพักอาจเป็นเรื่องยากเช่นเดียวกับการสร้างเนื้อหาแบบไม่มีช่วงพัก เนื่องจากไม่มีวิธีการจัดเก็บมาตรฐาน ด้านล่างนี้เราจะอธิบายวิธีที่โปรแกรมเปลี่ยนไฟล์ 2 โปรแกรมที่ใช้กันมากที่สุดอย่าง LAME และ iTunes จัดเก็บข้อมูลเมตาแบบไม่มีช่วงพัก มาเริ่มกันด้วยการตั้งค่าเมธอดตัวช่วยและเค้าโครงสําหรับ ParseGaplessData() ที่ใช้ด้านบน

// Since most MP3 encoders store the gapless metadata in binary, we'll need a
// method for turning bytes into integers.  Note: This doesn't work for values
// larger than 2^30 since we'll overflow the signed integer type when shifting.
function ReadInt(buffer) {
    var result = buffer.charCodeAt(0);
    for (var i = 1; i < buffer.length; ++i) {
    result <<../= 8;
    result += buffer.charCodeAt(i);
    }
    return result;
}

function ParseGaplessData(arrayBuffer) {
    // Gapless data is generally within the first 512 bytes, so limit parsing.
    var byteStr = new TextDecoder().decode(arrayBuffer.slice(0, 512));

    var frontPadding = 0, endPadding = 0, realSamples = 0;

    // ... we'll fill this in as we go below.

เราจะพูดถึงรูปแบบข้อมูลเมตา iTunes ของ Apple ก่อน เนื่องจากเป็นรูปแบบที่แยกวิเคราะห์และอธิบายได้ง่ายที่สุด ภายในไฟล์ MP3 และ M4A นั้น iTunes (และ afconvert) จะเขียนส่วนสั้นๆ เป็น ASCII ดังนี้

iTunSMPB[ 26 bytes ]0000000 00000840 000001C0 0000000000046E00

ซึ่งจะเขียนไว้ในแท็ก ID3 ภายในคอนเทนเนอร์ MP3 และภายในอะตอมข้อมูลเมตาภายในคอนเทนเนอร์ MP4 สําหรับวัตถุประสงค์ของเรา เราจะละเว้นโทเค็น 0000000 แรกได้ โทเค็น 3 รายการถัดไปคือการถอดส่วนหน้า การถอดส่วนท้าย และจำนวนตัวอย่างทั้งหมดที่ไม่มีการถอดส่วนหน้าและส่วนท้าย การหารแต่ละค่าเหล่านี้ด้วยอัตราตัวอย่างของเสียงจะให้ระยะเวลาของแต่ละค่า

// iTunes encodes the gapless data as hex strings like so:
//
//    'iTunSMPB[ 26 bytes ]0000000 00000840 000001C0 0000000000046E00'
//    'iTunSMPB[ 26 bytes ]####### frontpad  endpad    real samples'
//
// The approach here elides the complexity of actually parsing MP4 atoms. It
// may not work for all files without some tweaks.
var iTunesDataIndex = byteStr.indexOf('iTunSMPB');
if (iTunesDataIndex != -1) {
    var frontPaddingIndex = iTunesDataIndex + 34;
    frontPadding = parseInt(byteStr.substr(frontPaddingIndex, 8), 16);

    var endPaddingIndex = frontPaddingIndex + 9;
    endPadding = parseInt(byteStr.substr(endPaddingIndex, 8), 16);

    var sampleCountIndex = endPaddingIndex + 9;
    realSamples = parseInt(byteStr.substr(sampleCountIndex, 16), 16);
}

ในทางกลับกัน โปรแกรมเปลี่ยนไฟล์ MP3 แบบโอเพนซอร์สส่วนใหญ่จะจัดเก็บข้อมูลเมตาแบบไม่มีช่วงพักภายในส่วนหัว Xing พิเศษซึ่งวางไว้ในเฟรม MPEG ที่ไม่มีเสียง (ไม่มีเสียงเพื่อให้โปรแกรมถอดรหัสที่ไม่เข้าใจส่วนหัว Xing เล่นเสียงเงียบ) ขออภัย แท็กนี้อาจไม่แสดงเสมอไปและมีช่องที่ไม่บังคับหลายช่อง ในการสาธิตนี้ เราควบคุมสื่อได้ แต่ในทางปฏิบัติจะต้องทำการตรวจสอบเพิ่มเติมเพื่อดูว่าข้อมูลเมตาแบบไม่มีช่วงพักพร้อมใช้งานจริงเมื่อใด

ก่อนอื่น เราจะแยกวิเคราะห์จํานวนตัวอย่างทั้งหมด เราจะอ่านข้อมูลนี้จากส่วนหัว Xing เพื่อลดความซับซ้อน แต่ข้อมูลนี้อาจสร้างขึ้นจากส่วนหัวเสียง MPEG ปกติ ส่วนหัว Xing สามารถทำเครื่องหมายด้วยแท็ก Xing หรือ Info หลังจากแท็กนี้ 4 ไบต์พอดีจะมี 32 บิตที่แสดงจํานวนเฟรมทั้งหมดในไฟล์ การคูณค่านี้ด้วยจํานวนตัวอย่างต่อเฟรมจะให้จํานวนตัวอย่างทั้งหมดในไฟล์

// Xing padding is encoded as 24bits within the header.  Note: This code will
// only work for Layer3 Version 1 and Layer2 MP3 files with XING frame counts
// and gapless information.  See the following document for more details:
// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
var xingDataIndex = byteStr.indexOf('Xing');
if (xingDataIndex == -1) xingDataIndex = byteStr.indexOf('Info');
if (xingDataIndex != -1) {
    // See section 2.3.1 in the link above for the specifics on parsing the Xing
    // frame count.
    var frameCountIndex = xingDataIndex + 8;
    var frameCount = ReadInt(byteStr.substr(frameCountIndex, 4));

    // For Layer3 Version 1 and Layer2 there are 1152 samples per frame.  See
    // section 2.1.5 in the link above for more details.
    var paddedSamples = frameCount * 1152;

    // ... we'll cover this below.

เมื่อทราบจํานวนตัวอย่างทั้งหมดแล้ว เราจึงสามารถอ่านจํานวนตัวอย่างการเติมข้อมูลได้ ข้อมูลนี้อาจเขียนอยู่ภายใต้แท็ก LAME หรือ Lavf ที่ฝังอยู่ในส่วนหัว Xing ทั้งนี้ขึ้นอยู่กับโปรแกรมเปลี่ยนไฟล์ของคุณ หลังจากส่วนหัวนี้ 17 ไบต์พอดี จะมี 3 ไบต์ที่แสดงการเติมหน้าและท้าย 12 บิตตามลำดับ

xingDataIndex = byteStr.indexOf('LAME');
if (xingDataIndex == -1) xingDataIndex = byteStr.indexOf('Lavf');
if (xingDataIndex != -1) {
    // See http://gabriel.mp3-tech.org/mp3infotag.html#delays for details of
    // how this information is encoded and parsed.
    var gaplessDataIndex = xingDataIndex + 21;
    var gaplessBits = ReadInt(byteStr.substr(gaplessDataIndex, 3));

    // Upper 12 bits are the front padding, lower are the end padding.
    frontPadding = gaplessBits >> 12;
    endPadding = gaplessBits & 0xFFF;
}

realSamples = paddedSamples - (frontPadding + endPadding);
}

return {
audioDuration: realSamples * SECONDS_PER_SAMPLE,
frontPaddingDuration: frontPadding * SECONDS_PER_SAMPLE
};
}

ด้วยเหตุนี้ เราจึงมีฟังก์ชันที่สมบูรณ์สำหรับการแยกวิเคราะห์เนื้อหาส่วนใหญ่ที่ไม่มีช่วงพัก อย่างไรก็ตาม กรณีขอบเขตมีมากมาย ดังนั้นจึงขอแนะนำให้ใช้ความระมัดระวังก่อนใช้โค้ดที่คล้ายกันในเวอร์ชันที่ใช้งานจริง

ภาคผนวก ค: เกี่ยวกับการเก็บขยะ

ระบบจะเก็บขยะหน่วยความจำของอินสแตนซ์ SourceBuffer อย่างสม่ำเสมอตามประเภทเนื้อหา ขีดจํากัดเฉพาะของแพลตฟอร์ม และตําแหน่งการเล่นปัจจุบัน ใน Chrome ระบบจะเรียกคืนหน่วยความจำจากบัฟเฟอร์ที่เล่นไปแล้วก่อน อย่างไรก็ตาม หากการใช้งานหน่วยความจําเกินขีดจํากัดเฉพาะแพลตฟอร์ม ระบบจะนำหน่วยความจําออกจากบัฟเฟอร์ที่ยังไม่ได้เล่น

เมื่อการเล่นถึงช่องว่างในไทม์ไลน์เนื่องจากหน่วยความจำที่เรียกคืน การเล่นอาจกระตุกหากช่องว่างมีขนาดเล็กมาก หรือหยุดชะงักไปเลยหากช่องว่างมีขนาดใหญ่เกินไป ทั้งสองกรณีไม่ใช่ประสบการณ์การใช้งานที่ดีสำหรับผู้ใช้ ดังนั้นจึงควรหลีกเลี่ยงการเพิ่มข้อมูลมากเกินไปในครั้งเดียวและนำช่วงที่ไม่จำเป็นออกจากไทม์ไลน์สื่อด้วยตนเอง

คุณสามารถนำช่วงออกได้โดยใช้เมธอด remove() ใน SourceBuffer แต่ละรายการ ซึ่งจะใช้ช่วง [start, end] เป็นวินาที เช่นเดียวกับ appendBuffer() แต่ละ remove() จะเรียกเหตุการณ์ updateend ให้แสดงเมื่อดำเนินการเสร็จสมบูรณ์ ไม่ควรนําออกหรือเพิ่มส่วนอื่นๆ จนกว่าเหตุการณ์จะเริ่มต้น

ใน Chrome บนเดสก์ท็อป คุณสามารถเก็บเนื้อหาเสียงประมาณ 12 เมกะไบต์และเนื้อหาวิดีโอประมาณ 150 เมกะไบต์ไว้ในหน่วยความจำได้พร้อมกัน คุณไม่ควรใช้ค่าเหล่านี้ในเบราว์เซอร์หรือแพลตฟอร์มต่างๆ เช่น ค่าเหล่านี้ไม่ได้แสดงถึงอุปกรณ์เคลื่อนที่

การเก็บขยะจะส่งผลต่อข้อมูลที่เพิ่มลงใน SourceBuffers เท่านั้น โดยไม่มีการจํากัดจํานวนข้อมูลที่คุณสามารถบัฟเฟอร์ไว้ในตัวแปร JavaScript นอกจากนี้ คุณยังเพิ่มข้อมูลเดิมต่อท้ายในตำแหน่งเดิมซ้ำได้หากจำเป็น