การสร้างอุโมงค์สำหรับมัลติมีเดีย

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

  • สำหรับการเล่นวิดีโอแบบออนดีมานด์ใน Android 5 ขึ้นไป AudioTrack นาฬิกาที่ซิงค์กับการประทับเวลาการนำเสนอเสียงที่แอปส่งเข้ามา

  • สำหรับการเล่นการถ่ายทอดสดใน Android 11 ขึ้นไป นาฬิกาอ้างอิงโปรแกรม (PCR) หรือนาฬิกาเวลาของระบบ (STC) ที่ขับเคลื่อนโดย จูนเนอร์

ฉากหลัง

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

เนื่องจากการเล่นวิดีโอแบบ Tunnel จะข้ามโค้ดของแอปและลดจำนวน กระบวนการที่ทำงานกับวิดีโอ จึงทำให้การแสดงวิดีโอมีประสิทธิภาพมากขึ้น ขึ้นอยู่กับการติดตั้งใช้งานของผู้ผลิตอุปกรณ์ นอกจากนี้ ยังช่วยให้จังหวะและเวลาในการซิงค์วิดีโอแม่นยำยิ่งขึ้นกับนาฬิกาที่เลือก (PRC, STC หรือเสียง) โดยหลีกเลี่ยงปัญหาด้านเวลาที่อาจเกิดขึ้นเนื่องจากความคลาดเคลื่อนระหว่างเวลาที่คำขอของ Android ในการแสดงวิดีโอและเวลาของ Vsync ฮาร์ดแวร์จริง อย่างไรก็ตาม การทำอุโมงค์ยังอาจลดการรองรับเอฟเฟกต์ GPU เช่น การเบลอหรือมุมโค้งในหน้าต่างภาพซ้อนภาพ (PIP) เนื่องจากบัฟเฟอร์จะข้ามสแต็กกราฟิกของ Android

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

การเปรียบเทียบโหมดดั้งเดิมและโหมดอุโมงค์

รูปที่ 1 การเปรียบเทียบกระบวนการเล่นวิดีโอแบบไม่ใช้และใช้การเชื่อมต่อแบบอุโมงค์

สำหรับนักพัฒนาแอป

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

หากต้องการเล่นวิดีโอแบบออนดีมานด์ใน Android 5 ขึ้นไป ให้ทำดังนี้

  1. สร้างอินสแตนซ์ SurfaceView

  2. สร้างอินสแตนซ์ audioSessionId

  3. สร้างอินสแตนซ์ AudioTrack และ MediaCodec ด้วยอินสแตนซ์ audioSessionId ที่สร้างในขั้นตอนที่ 2

  4. จัดคิวข้อมูลเสียงไปยัง AudioTrack พร้อมการประทับเวลาของงานนำเสนอสำหรับ เฟรมเสียงแรกในข้อมูลเสียง

หากต้องการเล่นการถ่ายทอดสดใน Android 11 ขึ้นไป ให้ทำดังนี้

  1. สร้างอินสแตนซ์ SurfaceView

  2. รับอินสแตนซ์ avSyncHwId จาก Tuner

  3. สร้างอินสแตนซ์ AudioTrack และ MediaCodec ด้วยอินสแตนซ์ avSyncHwId ที่สร้างในขั้นตอนที่ 2

ลำดับการเรียก API แสดงอยู่ในข้อมูลโค้ดต่อไปนี้

aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);

// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
  return FAILURE;
}

// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);

ลักษณะการทำงานของการเล่นวิดีโอออนดีมานด์

เนื่องจากการเล่นวิดีโอออนดีมานด์ที่ส่งผ่านอุโมงค์เชื่อมโยงกับการAudioTrack เล่นโดยนัย ลักษณะการทำงานของการเล่นวิดีโอที่ส่งผ่านอุโมงค์จึงอาจขึ้นอยู่กับลักษณะการทำงาน ของการเล่นเสียง

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

    • หากต้องการส่งสัญญาณว่าควรแสดงเฟรมวิดีโอแรกที่อยู่ในคิวทันทีที่ถอดรหัสแล้ว ให้ตั้งค่าพารามิเตอร์ PARAMETER_KEY_TUNNEL_PEEK เป็น 1 เมื่อมีการจัดลำดับเฟรมวิดีโอที่บีบอัดใหม่ในคิว (เช่น เมื่อมีB-frame ) หมายความว่าเฟรมวิดีโอแรกที่แสดงควรเป็น I-frame เสมอ

    • หากไม่ต้องการให้แสดงเฟรมวิดีโอแรกที่อยู่ในคิวจนกว่าการเล่นเสียงจะเริ่ม ให้ตั้งค่าพารามิเตอร์นี้เป็น 0

    • หากไม่ได้ตั้งค่าพารามิเตอร์นี้ OEM จะเป็นผู้กำหนดลักษณะการทำงานของอุปกรณ์

  • เมื่อไม่ได้ระบุข้อมูลเสียงให้กับ AudioTrack และบัฟเฟอร์ว่างเปล่า (เสียงขาดหาย) การเล่นวิดีโอจะหยุดชะงักจนกว่าจะมีการเขียนข้อมูลเสียงเพิ่มเติม เนื่องจากนาฬิกาเสียงไม่เดินหน้าอีกต่อไป

  • ในระหว่างการเล่น การหยุดชะงักที่แอปแก้ไขไม่ได้อาจปรากฏใน การประทับเวลาการนำเสนอเสียง ในกรณีนี้ OEM จะแก้ไขช่องว่างเชิงลบโดยหยุดเฟรมวิดีโอปัจจุบันชั่วคราว และแก้ไขช่องว่างเชิงบวกโดยทิ้งเฟรมวิดีโอหรือแทรกเฟรมเสียงที่ไม่มีเสียง (ขึ้นอยู่กับการติดตั้งใช้งานของ OEM) ตำแหน่งเฟรม AudioTimestamp จะไม่เพิ่มขึ้นสำหรับ เฟรมเสียงเงียบที่แทรก

ลำดับการทำงานของการกรอวิดีโออย่างละเอียด

การกรอวิดีโออย่างละเอียดช่วยให้คุณค้นหาช่วงที่เฉพาะเจาะจงในวิดีโอได้ การกรอวิดีโออย่างละเอียดจะแสดงวิดีโอที่การประทับเวลาที่ขออย่างแน่นอน ซึ่งแตกต่างจากการกรอวิดีโอด้วยคีย์เฟรม ซึ่งจะข้ามไปยัง I-frame ที่ใกล้ที่สุดเท่านั้นและอาจเบี่ยงเบนจากตำแหน่งเป้าหมาย ไปหลายวินาที การปฏิบัติตามลำดับ API ที่เฉพาะเจาะจงนี้จะช่วยให้แอป เล่นโฆษณาก่อนวิดีโอแบบพื้นหลังและซิงค์เวลาได้อย่างราบรื่น ซึ่งจะช่วยให้เฟรมเป้าหมายแสดงทันทีเมื่อเล่นต่อ

หากต้องการกรอวิดีโออย่างละเอียด ให้ทำตามลำดับการดำเนินการที่แสดงในรูปที่ 2

โฟลว์ลำดับการกรอ

รูปที่ 2 ลำดับการไหลเพื่อให้กรอวิดีโอได้อย่างแม่นยำ

รายละเอียดที่สำคัญมีดังนี้

  • การดำเนินการแบบขนาน: คุณสามารถดำเนินการตามขั้นตอนภายในparกล่องเดียว พร้อมกันได้ เช่น วิดีโอคอล MediaCodec จะไม่ขึ้นอยู่กับ AudioTrack

  • การขึ้นต่อกันตามลำดับ: เรียกใช้การดำเนินการทั้งหมดภายในparกล่องแรก ก่อนที่จะไปยังparกล่องที่สอง กล่าวโดยละเอียดคือ แอปต้องตรวจสอบว่า AudioTrack.write และบัฟเฟอร์ในวิดีโอ MediaCodec ได้รับการจัดคิวไว้ก่อนที่จะเรียกใช้ AudioTrack.play

ลำดับการเล่นแบบความเร็วผันแปร

การเล่นด้วยความเร็วที่เปลี่ยนแปลงได้ช่วยให้คุณเล่นวิดีโอได้เร็วขึ้นหรือช้าลงกว่า ความเร็วปกติ โดยปกติแล้ว แอปมักใช้ฟีเจอร์นี้เพื่อให้ผู้ใช้รับชมเนื้อหาได้เร็วขึ้น (เช่น เล่นการบรรยายเพื่อการศึกษาหรือพอดแคสต์ที่ 1.5x หรือ 2.0x เพื่อประหยัดเวลา) หรือช้าลง (เช่น วิเคราะห์การเล่นกีฬาหรือวิดีโอแนะนำที่ 0.5x)

หากต้องการตั้งค่าความเร็ว ให้ทำตามลำดับการดำเนินการที่แสดงในรูปที่ 3

โฟลว์ลำดับความเร็ว

รูปที่ 3 ลำดับการตั้งค่าความเร็ว

ลักษณะการทำงานและข้อกำหนดทางเทคนิคต่อไปนี้ไม่ได้แสดงไว้ใน แผนภาพลำดับที่ 3

  • AudioTrack.getTimestamp จะแสดงผล framePosition ตามความถี่ของอินพุตเสียงต้นฉบับ เช่น เมื่อมีอินพุต 44100 Hz และความเร็วในการเล่น 2.0x หลังจากเล่นไป 2 วินาที AudioTrack.getTimestamp จะแสดงผล framePosition เป็น 176400

  • หากแอปเรียกใช้ setSpeed(1.5) และสำเร็จ จากนั้นแอป เรียกใช้ setSpeed(30) และไม่สำเร็จ ความเร็วในการเล่นจะยังคงอยู่ที่ 1.5 เท่า

  • หากปิดเสียง (โดยใช้ setVolume) แอปยังคงต้องส่งบัฟเฟอร์เสียงเนื่องจากเฟรมวิดีโอแสดงผลตามตำแหน่งเสียง

  • ระบบจะคงระดับเสียงไว้เมื่อเปลี่ยนความเร็ว

  • ความเร็วในการเล่นจะไม่ได้รับผลกระทบจากการดำเนินการเล่นอื่นๆ

    • ตัวอย่างที่ 1: หากความเร็วในการเล่นคือ 1.5 เท่าและAudioTrack หยุดชั่วคราว ความเร็วจะยังคงอยู่ที่ 1.5 เท่าหลังจากเล่น AudioTrack ต่อ

    • ตัวอย่างที่ 2: หากความเร็วในการเล่นเป็น 1.5 เท่าและผู้ใช้กรอไปยัง PTS อื่น การเล่นจะยังคงอยู่ที่ 1.5 เท่าตามรูปที่ 2

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

    • ตัวอย่างเช่น หากอัตราเฟรมต้นฉบับของเนื้อหาคือ 60 fps และความเร็วในการเล่นคือ 2 เท่า ให้ตั้งค่า KEY_OPERATING_RATE เป็น 120
  • การตั้งค่าความเร็วซ้ำๆ ด้วยความเร็วที่รองรับต่างๆ ไม่ควรทำให้เกิดข้อผิดพลาดใดๆ และลักษณะการทำงานของการเล่นหลังจากการเรียกครั้งสุดท้ายควรเหมือนกับกรณีที่ตั้งค่าความเร็วเพียงครั้งเดียวเป็นการตั้งค่าความเร็วล่าสุด

สำหรับผู้ผลิตอุปกรณ์

การกำหนดค่า

OEM ควรสร้างตัวถอดรหัสวิดีโอแยกต่างหากเพื่อรองรับการเล่นวิดีโอแบบ Tunnel ตัวถอดรหัสนี้ควรโฆษณาว่าสามารถเล่นแบบ Tunnel ได้ในไฟล์ media_codecs.xml

<Feature name="tunneled-playback" required="true"/>

เมื่อกำหนดค่าอินสแตนซ์ MediaCodec ที่มีการทำอุโมงค์ด้วยรหัสเซสชันเสียง อินสแตนซ์จะ ค้นหา AudioFlinger สำหรับรหัส HW_AV_SYNC นี้

if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
    int sessionId = 0;
    try {
        sessionId = (Integer)entry.getValue();
    }
    catch (Exception e) {
        throw new IllegalArgumentException("Wrong Session ID Parameter!");
    }
    keys[i] = "audio-hw-sync";
    values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
}

ในระหว่างการค้นหานี้ AudioFlinger จะดึงข้อมูลHW_AV_SYNCรหัส จากอุปกรณ์เสียงหลักและเชื่อมโยงรหัสดังกล่าวกับรหัสเซสชันเสียง ภายใน

audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC);
AudioParameter param = AudioParameter(String8(reply));
int hwAVSyncId;
param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);

หากสร้างอินสแตนซ์ AudioTrack แล้ว ระบบจะส่งรหัส HW_AV_SYNC ไปยังสตรีมเอาต์พุตที่มีรหัสเซสชันเสียงเดียวกัน หากยังไม่ได้สร้าง ระบบจะส่ง HW_AV_SYNC ID ไปยังสตรีมเอาต์พุตในระหว่างการสร้างAudioTrack โดยเธรดการเล่นจะดำเนินการดังนี้

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

ระบบจะส่งรหัส HW_AV_SYNC ไม่ว่าจะตรงกับสตรีมเอาต์พุตเสียงหรือการกำหนดค่า Tuner ไปยังคอมโพเนนต์ OMX หรือ Codec2 เพื่อให้โค้ด OEM เชื่อมโยงตัวแปลงรหัสกับสตรีมเอาต์พุตเสียงที่เกี่ยวข้องหรือสตรีมจูนเนอร์ได้

ในระหว่างการกำหนดค่าคอมโพเนนต์ คอมโพเนนต์ OMX หรือ Codec2 ควรแสดงแฮนเดิลแถบด้านข้างที่ใช้เชื่อมโยงตัวแปลงรหัสกับเลเยอร์ Hardware Composer (HWC) ได้ เมื่อแอปเชื่อมโยงพื้นผิวกับ MediaCodec ระบบจะส่งแฮนเดิลแถบข้างนี้ไปยัง HWC ผ่าน SurfaceFlinger ซึ่งจะกำหนดค่าเลเยอร์เป็นเลเยอร์แถบข้าง

err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle);
if (err != OK) {
  ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err);
  return err;
}

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

OMX

คอมโพเนนต์ตัวถอดรหัสที่ส่งผ่านอุโมงค์ควรจะรองรับสิ่งต่อไปนี้

  • การตั้งค่าพารามิเตอร์ OMX.google.android.index.configureVideoTunnelMode extended ซึ่งใช้โครงสร้าง ConfigureVideoTunnelModeParams เพื่อส่ง ในรหัส HW_AV_SYNC ที่เชื่อมโยงกับอุปกรณ์เอาต์พุตเสียง

  • การกำหนดค่าพารามิเตอร์ OMX_IndexConfigAndroidTunnelPeek ที่บอกให้ตัวแปลงรหัส แสดงผลหรือไม่แสดงผลเฟรมวิดีโอแรกที่ถอดรหัสแล้ว ไม่ว่า การเล่นเสียงจะเริ่มแล้วหรือไม่ก็ตาม

  • ส่งเหตุการณ์ OMX_EventOnFirstTunnelFrameReady เมื่อถอดรหัสเฟรมวิดีโอแรกที่ผ่านการอุโมงค์ และพร้อมที่จะแสดงผล

การใช้งาน AOSP จะกำหนดค่าโหมดอุโมงค์ใน ACodec ผ่าน OMXNodeInstance ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

OMX_INDEXTYPE index;
OMX_STRING name = const_cast<OMX_STRING>(
        "OMX.google.android.index.configureVideoTunnelMode");

OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);

ConfigureVideoTunnelModeParams tunnelParams;
InitOMXParams(&tunnelParams);
tunnelParams.nPortIndex = portIndex;
tunnelParams.bTunneled = tunneled;
tunnelParams.nAudioHwSync = audioHwSync;
err = OMX_SetParameter(mHandle, index, &tunnelParams);
err = OMX_GetParameter(mHandle, index, &tunnelParams);
sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;

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

Codec2

ใน Android 11 ขึ้นไป Codec2 รองรับการเล่นแบบ Tunnel คอมโพเนนต์ตัวถอดรหัส ควรรองรับสิ่งต่อไปนี้

  • การกำหนดค่า C2PortTunneledModeTuning ซึ่งกำหนดค่าโหมดอุโมงค์และ ส่งผ่านใน HW_AV_SYNC ที่ดึงมาจากอุปกรณ์เอาต์พุตเสียงหรือ การกำหนดค่าจูนเนอร์

  • การค้นหา C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE เพื่อจัดสรรและเรียกข้อมูล แฮนเดิลแถบข้างสำหรับ HWC

  • การจัดการ C2_PARAMKEY_TUNNEL_HOLD_RENDER เมื่อแนบไปกับ C2Work ซึ่ง สั่งให้ตัวแปลงรหัสถอดรหัสและส่งสัญญาณว่าการทำงานเสร็จสมบูรณ์ แต่ไม่ให้แสดงผล บัฟเฟอร์เอาต์พุตจนกว่า 1) จะมีการสั่งให้ตัวแปลงรหัสแสดงผลในภายหลัง หรือ 2) การเล่นเสียงจะเริ่มขึ้น

  • การจัดการ C2_PARAMKEY_TUNNEL_START_RENDER ซึ่งสั่งให้ตัวแปลงรหัส แสดงเฟรมที่ทำเครื่องหมายด้วย C2_PARAMKEY_TUNNEL_HOLD_RENDER ทันที แม้ว่าการเล่นเสียงจะยังไม่เริ่มก็ตาม

  • ปล่อยให้ debug.stagefright.ccodec_delayed_params ไม่ได้กำหนดค่า (แนะนำ) หากกำหนดค่า ให้ตั้งเป็น false

การติดตั้งใช้งาน AOSP จะกำหนดค่าโหมด Tunnel ใน CCodec ผ่าน C2PortTunnelModeTuning ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
    tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
        failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
        C2_DONT_BLOCK, &params);
if (c2err == C2_OK && params.size() == 1u) {
    C2PortTunnelHandleTuning::output *videoTunnelSideband =
            C2PortTunnelHandleTuning::output::From(params[0].get());
    return OK;
}

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

HAL เสียง

สำหรับการเล่นวิดีโอแบบออนดีมานด์ HAL เสียงจะได้รับการนำเสนอเสียง การประทับเวลาแบบอินไลน์พร้อมกับข้อมูลเสียงในรูปแบบ Big-Endian ภายในส่วนหัวที่พบ ที่จุดเริ่มต้นของแต่ละบล็อกของข้อมูลเสียงที่แอปเขียน

struct TunnelModeSyncHeader {
  // The 32-bit data to identify the sync header (0x55550002)
  int32 syncWord;
  // The size of the audio data following the sync header before the next sync
  // header might be found.
  int32 sizeInBytes;
  // The presentation timestamp of the first audio sample following the sync
  // header.
  int64 presentationTimestamp;
  // The number of bytes to skip after the beginning of the sync header to find the
  // first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
  // to the channel count and sample size).
  int32 offset;
}

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

หยุดการสนับสนุนชั่วคราว

Android 5 หรือต่ำกว่าไม่รองรับการหยุดชั่วคราว คุณจะหยุดการเล่นที่ส่งผ่านอุโมงค์ชั่วคราวได้ก็ต่อเมื่อ A/V ขาดแคลนเท่านั้น แต่หากบัฟเฟอร์ภายในสำหรับวิดีโอมีขนาดใหญ่ (เช่น มีข้อมูล 1 วินาทีในคอมโพเนนต์ OMX) การหยุดชั่วคราวจะดูเหมือนไม่ตอบสนอง

ใน Android 5.1 ขึ้นไป AudioFlinger รองรับการหยุดชั่วคราวและเล่นต่อสำหรับเอาต์พุตเสียงโดยตรง (แบบอุโมงค์) หาก HAL ใช้การหยุดชั่วคราวและกลับมาทำงานต่อ ระบบจะส่งต่อการหยุดชั่วคราว และกลับมาทำงานต่อไปยัง HAL

ลำดับการเรียกใช้ pause, flush, resume จะได้รับการพิจารณาโดยการเรียกใช้ HAL ในเธรดการเล่น (เช่นเดียวกับการออฟโหลด)

คำแนะนำในการใช้งาน

HAL เสียง

สำหรับ Android 11 สามารถใช้รหัสการซิงค์ HW จาก PCR หรือ STC สำหรับการซิงค์ A/V ได้ ดังนั้นระบบจึงรองรับสตรีมวิดีโอเท่านั้น

สำหรับ Android 10 หรือต่ำกว่า อุปกรณ์ที่รองรับการเล่นวิดีโอแบบ Tunnel ควรมี โปรไฟล์สตรีมเอาต์พุตเสียงอย่างน้อย 1 รายการที่มีแฟล็ก FLAG_HW_AV_SYNC และ AUDIO_OUTPUT_FLAG_DIRECT ในไฟล์ audio_policy.conf ค่าสถานะเหล่านี้ ใช้เพื่อตั้งค่านาฬิกาของระบบจากนาฬิกาเสียง

OMX

ผู้ผลิตอุปกรณ์ควรมีคอมโพเนนต์ OMX แยกต่างหากสำหรับการเล่นวิดีโอแบบ Tunneling (ผู้ผลิตอาจมีคอมโพเนนต์ OMX เพิ่มเติมสำหรับการเล่นเสียงและวิดีโอประเภทอื่นๆ เช่น การเล่นที่ปลอดภัย) คอมโพเนนต์ที่ผ่านอุโมงค์ ควรมีลักษณะดังนี้

  • ระบุบัฟเฟอร์ 0 (nBufferCountMin, nBufferCountActual) ในเอาต์พุต พอร์ต

  • ติดตั้งใช้งานส่วนขยาย OMX.google.android.index.prepareForAdaptivePlayback setParameter

  • ระบุความสามารถในไฟล์ media_codecs.xml และประกาศฟีเจอร์การเล่นแบบอุโมงค์ นอกจากนี้ ควรระบุข้อจำกัดเกี่ยวกับขนาด เฟรม การจัดแนว หรือบิตเรตด้วย ตัวอย่างแสดงอยู่ด้านล่าง

    <MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled"
    type="video/avc" >
        <Feature name="adaptive-playback" />
        <Feature name="tunneled-playback" required=true />
        <Limit name="size" min="32x32" max="3840x2160" />
        <Limit name="alignment" value="2x2" />
        <Limit name="bitrate" range="1-20000000" />
            ...
    </MediaCodec>
    

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

<MediaCodec name="OMX._OEM\_NAME_.VIDEO.DECODER.AVC" type="video/avc" >
    <Feature name="adaptive-playback" />
    <Feature name="tunneled-playback" />
    <Limit name="size" min="32x32" max="3840x2160" />
    <Limit name="alignment" value="2x2" />
    <Limit name="bitrate" range="1-20000000" />
        ...
</MediaCodec>

คอมโพสเซอร์ฮาร์ดแวร์ (HWC)

เมื่อมีเลเยอร์ที่ส่งผ่านอุโมงค์ (เลเยอร์ที่มี HWC_SIDEBAND compositionType) ใน จอแสดงผล sidebandStream ของเลเยอร์จะเป็นแฮนเดิลแถบข้างที่จัดสรรโดย คอมโพเนนต์วิดีโอ OMX

HWC จะซิงค์เฟรมวิดีโอที่ถอดรหัสแล้ว (จากคอมโพเนนต์ OMX ที่ส่งผ่านอุโมงค์) กับ แทร็กเสียงที่เกี่ยวข้อง (ที่มี audio-hw-sync ID) เมื่อเฟรมวิดีโอใหม่ กลายเป็นเฟรมปัจจุบัน HWC จะรวมเฟรมดังกล่าวกับเนื้อหาปัจจุบันของเลเยอร์ทั้งหมด ที่ได้รับระหว่างการเรียกใช้ฟังก์ชันเตรียมหรือตั้งค่าครั้งล่าสุด แล้วแสดงรูปภาพที่ได้ การเรียกใช้ prepare หรือ set จะเกิดขึ้นเมื่อเลเยอร์อื่นๆ เปลี่ยนแปลง หรือเมื่อพร็อพเพอร์ตี้ของเลเยอร์แถบข้าง (เช่น ตำแหน่งหรือขนาด) เปลี่ยนแปลงเท่านั้น

รูปต่อไปนี้แสดงให้เห็นว่า HWC ทำงานร่วมกับตัวซิงโครไนซ์ฮาร์ดแวร์ (หรือเคอร์เนลหรือ ไดรเวอร์) เพื่อรวมเฟรมวิดีโอ (7b) กับการคอมโพสล่าสุด (7a) เพื่อแสดงในเวลาที่ถูกต้องตามเสียง (7c)

HWC ที่รวมเฟรมวิดีโอตามเสียง

รูปที่ 4 ตัวซิงค์ฮาร์ดแวร์ (หรือเคอร์เนลหรือไดรเวอร์) ของ HWC