บทนำ
ส่วนขยายแหล่งที่มาของสื่อ (MSE) มีการบัฟเฟอร์เพิ่มเติมและการควบคุมการเล่นสำหรับองค์ประกอบ HTML5 <audio>
และ <video>
แม้ว่าเดิมทีโปรแกรมจะพัฒนามาเพื่อช่วยให้โปรแกรมเล่นวิดีโอที่ใช้ Dynamic Adaptive Streaming ผ่าน 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]
แล้วอะไรคือตัวอย่างศูนย์ (เงียบ) พวกนั้น! แต่จริงๆ แล้วเกิดจากอาร์ติแฟกต์การบีบอัดที่เกิดขึ้นระหว่างการเข้ารหัส โปรแกรมเปลี่ยนไฟล์เกือบทุกโปรแกรมจะมีระยะห่างจากขอบบางประเภท ในกรณีนี้ LAME ได้เพิ่มตัวอย่าง Padding จำนวน 576 ตัวอย่างต่อท้ายไฟล์
นอกจากระยะห่างจากขอบที่ด้านท้ายแล้ว แต่ละไฟล์ยังมีระยะห่างจากขอบที่ด้านหน้าเพิ่มขึ้นอีกด้วย หากเราสอดส่องแทร็ก sintel_1.mp3
ไปด้านหน้า ก็จะเห็นตัวอย่างระยะห่างจากขอบอีก 576 ตัวอย่าง ระยะห่างจากขอบจะแตกต่างกันตามโปรแกรมเปลี่ยนไฟล์และเนื้อหา แต่เราทราบค่าที่แน่นอนโดยอิงจาก metadata
ที่รวมอยู่ในแต่ละไฟล์
ส่วนที่ไม่มีเสียงที่ตอนต้นและตอนท้ายของแต่ละไฟล์เป็นสาเหตุของข้อบกพร่องระหว่างส่วนต่างๆ ในเดโมก่อนหน้านี้ เราต้องนำส่วนที่ไร้เสียงเหล่านี้ออกเพื่อให้การเล่นไม่ราบรื่น โชคดีที่ 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);
}
รูปแบบคลื่นที่ลื่นไหล
มาดูกันว่าโค้ดใหม่ของเราประสบความสำเร็จอย่างไร โดยดู Waveform อีกครั้งหลังจากที่เราเพิ่มหน้าต่างต่อท้ายแล้ว ที่ด้านล่าง คุณจะเห็นว่าส่วนที่ปิดเสียงที่อยู่ท้าย 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
ขั้นแรก เราจะแยกแทร็ก 1-Snow_Fight.flac
ในช่วง 31.5 วินาทีแรก นอกจากนี้ เรายังต้องการเพิ่มระยะเวลาการค่อยๆ เปลี่ยน 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 เป็นวิธีที่ง่ายที่สุดเนื่องจากโปรแกรมเปลี่ยนไฟล์เกือบทุกประเภทจะรองรับการส่งผ่านข้อมูลของ 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.
ตอนนี้เรามีจำนวนตัวอย่างทั้งหมดแล้ว เราไปต่อกับการอ่านจำนวนตัวอย่าง Padding ได้ ซึ่งอาจเขียนภายใต้แท็ก 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
};
}
ด้วยการทำเช่นนั้น เราจึงมีฟังก์ชันที่สมบูรณ์ในการแยกวิเคราะห์เนื้อหาส่วนใหญ่ที่ไม่มีช่องว่าง อย่างไรก็ตาม กรณี Edge ก็มีอยู่มากมาย เราจึงขอแนะนำให้ใช้ความระมัดระวังก่อนที่จะใช้โค้ดที่คล้ายกันในเวอร์ชันที่ใช้งานจริง
ภาคผนวก ค: เกี่ยวกับการเก็บขยะ
หน่วยความจำที่เป็นของอินสแตนซ์ SourceBuffer
จะมีไฟล์ขยะที่รวบรวมอย่างต่อเนื่องตามประเภทเนื้อหา ขีดจำกัดเฉพาะแพลตฟอร์ม และตำแหน่งการเล่นปัจจุบัน ใน Chrome ระบบจะอ้างสิทธิ์ความทรงจำอีกครั้งจากบัฟเฟอร์ที่เล่นไปแล้วก่อน อย่างไรก็ตาม หากการใช้งานหน่วยความจำเกินขีดจำกัดเฉพาะแพลตฟอร์ม ระบบจะนำหน่วยความจำออกจากบัฟเฟอร์ที่ยังไม่ได้เล่น
เมื่อการเล่นถึงช่องว่างในไทม์ไลน์เนื่องจากหน่วยความจำที่เรียกคืน การเล่นอาจสะดุดหากช่องว่างนั้นเล็กพอหรือหยุดแสดงอย่างสิ้นเชิงหากช่องว่างมีขนาดใหญ่เกินไป การใช้งานเหล่านี้ไม่ใช่ประสบการณ์ที่ยอดเยี่ยมของผู้ใช้ คุณจึงควรหลีกเลี่ยงการเพิ่มข้อมูลพร้อมกันมากเกินไป และนำช่วงออกจากไทม์ไลน์ของสื่อที่ไม่จำเป็นอีกต่อไปด้วยตนเอง
คุณนำช่วงออกได้ผ่านทางเมธอด remove()
ใน SourceBuffer
แต่ละรายการ ซึ่งใช้เวลาช่วง [start, end]
เป็นวินาที remove()
แต่ละรายการจะเริ่มการทำงานของเหตุการณ์ updateend
เมื่อเหตุการณ์เสร็จสิ้น คล้ายกับ appendBuffer()
ไม่ควรออกหรือนำรายการอื่นออกหรือต่อท้ายจนกว่าเหตุการณ์จะเริ่มทำงาน
ใน Chrome บนเดสก์ท็อป คุณสามารถเก็บเนื้อหาเสียงได้ประมาณ 12 เมกะไบต์และเนื้อหาวิดีโอ 150 เมกะไบต์ในหน่วยความจำพร้อมกัน คุณไม่ควรใช้ค่าเหล่านี้ในเบราว์เซอร์หรือแพลตฟอร์มต่างๆ เช่น อาจจะไม่เป็นตัวแทนของอุปกรณ์เคลื่อนที่
การเก็บรวบรวมขยะส่งผลต่อข้อมูลที่เพิ่มไปยัง SourceBuffers
เท่านั้น ไม่มีขีดจำกัดปริมาณข้อมูลที่คุณสามารถเก็บไว้ในตัวแปร JavaScript หรือคุณจะใส่ข้อมูลเดิมอีกครั้งในตำแหน่งเดียวกันก็ได้หากจำเป็น