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

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 คุณจะได้ยินข้อบกพร่องที่จุดเหล่านี้

ข้อมูลประชากร

แย่จัง! นั่นไม่ใช่ประสบการณ์ดี เราปรับปรุงให้ดีขึ้นได้ การทำงานมากขึ้นอีกเล็กน้อย การใช้ไฟล์ 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]

ช่องโหว่ของ MP3

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

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

สิ้นสุด Gap ของ 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 (สีฟ้า) ออกแล้ว ทำให้การเปลี่ยนระหว่างกลุ่มต่างๆ เป็นไปอย่างราบรื่น

MP3 กลาง

บทสรุป

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

ข้อมูลประชากร

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

ขอขอบคุณที่อ่านข้อมูล

ภาคผนวก A: การสร้างเนื้อหาที่ไม่ขาดตอน

การสร้างเนื้อหาที่ไม่ขาดตอนอาจเป็นเรื่องยาก ด้านล่างนี้เราจะแนะนำการสร้างสื่อ 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 ได้ง่ายที่สุด เนื่องจากโปรแกรมเปลี่ยนไฟล์เกือบทั้งหมดรองรับการส่งผ่านข้อมูลของไฟล์ และเช่นเดียวกัน เราสามารถทำเช่นนี้กับ 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 ออกเป็นส่วนๆ พร้อมด้วยข้อมูลเมตาที่ถูกต้องซึ่งจำเป็นต่อการเล่นที่ไม่ขาดตอน ดูภาคผนวก B สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับลักษณะของข้อมูลเมตา

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

การแยกวิเคราะห์ข้อมูลเมตาที่ไม่มีช่องโหว่ก็อาจเป็นเรื่องยุ่งยากเช่นเดียวกับการสร้างเนื้อหาที่ไม่ขาดตอน เนื่องจากไม่มีวิธีการมาตรฐานในการจัดเก็บข้อมูล ด้านล่างนี้เราจะอธิบายวิธีจัดเก็บข้อมูลเมตา ที่ไร้ช่องโหว่ของโปรแกรมเปลี่ยนไฟล์ ที่พบได้ทั่วไป 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
      };
    }

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

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

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

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

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

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

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

ความคิดเห็น