Tạo đường hầm đa phương tiện

Tính năng tạo đường hầm đa phương tiện cho phép dữ liệu video nén truyền trực tiếp qua bộ giải mã video phần cứng đến màn hình mà không cần xử lý bằng mã ứng dụng hoặc mã khung Android. Mã dành riêng cho thiết bị bên dưới ngăn xếp Android xác định những khung hình video cần gửi đến màn hình và thời điểm gửi những khung hình đó bằng cách so sánh dấu thời gian trình chiếu khung hình video với một trong các loại đồng hồ bên trong sau đây:

  • Đối với tính năng phát video theo yêu cầu trong Android 5 trở lên, đồng hồ AudioTrack được đồng bộ hoá với dấu thời gian trình bày âm thanh do ứng dụng chuyển

  • Đối với tính năng phát nội dung truyền hình trực tiếp trong Android 11 trở lên, đồng hồ tham chiếu chương trình (PCR) hoặc đồng hồ thời gian hệ thống (STC) do bộ điều chỉnh điều khiển

Thông tin khái quát

Chế độ phát video truyền thống trên Android thông báo cho ứng dụng khi một khung video nén đã được giải mã. Sau đó, ứng dụng sẽ phát hành khung video đã giải mã cho màn hình để hiển thị tại cùng thời điểm đồng hồ hệ thống với khung âm thanh tương ứng, truy xuất các thực thể AudioTimestamps trước đây để tính toán thời gian chính xác.

Vì tính năng phát video được tạo đường hầm bỏ qua mã ứng dụng và giảm số lượng quy trình hoạt động trên video, nên tính năng này có thể cung cấp khả năng kết xuất video hiệu quả hơn tuỳ thuộc vào cách triển khai của OEM. Phiên bản này cũng có thể cung cấp chất lượng và đồng bộ hoá video chính xác hơn cho đồng hồ đã chọn (PRC, STC hoặc âm thanh) bằng cách tránh các sự cố về thời gian do sai lệch tiềm ẩn giữa thời gian yêu cầu Android kết xuất video và thời gian đồng bộ hoá phần cứng thực sự. Tuy nhiên, tính năng tạo đường hầm cũng có thể làm giảm khả năng hỗ trợ các hiệu ứng GPU như làm mờ hoặc bo tròn góc trong cửa sổ hình trong hình (PiP), vì các vùng đệm sẽ bỏ qua ngăn xếp đồ hoạ Android.

Sơ đồ sau đây cho thấy cách tạo đường hầm đơn giản hoá quá trình phát video.

so sánh chế độ truyền thống và chế độ đường hầm

Hình 1. So sánh quy trình phát video truyền thống và quy trình phát video qua đường hầm

Dành cho nhà phát triển ứng dụng

Vì hầu hết nhà phát triển ứng dụng đều tích hợp với một thư viện để triển khai tính năng phát, nên trong hầu hết các trường hợp, việc triển khai chỉ cần định cấu hình lại thư viện đó để phát qua đường hầm. Để triển khai trình phát video được định tuyến ở cấp thấp, hãy làm theo hướng dẫn sau.

Cách phát video theo yêu cầu trên Android 5 trở lên:

  1. Tạo một thực thể SurfaceView.

  2. Tạo một thực thể audioSessionId.

  3. Tạo các thực thể AudioTrackMediaCodec bằng thực thể audioSessionId đã tạo ở bước 2.

  4. Thêm dữ liệu âm thanh vào hàng đợi AudioTrack bằng dấu thời gian trình bày cho khung âm thanh đầu tiên trong dữ liệu âm thanh.

Cách phát trực tiếp trên Android 11 trở lên:

  1. Tạo một thực thể SurfaceView.

  2. Lấy một thực thể avSyncHwId từ Tuner.

  3. Tạo các thực thể AudioTrackMediaCodec bằng thực thể avSyncHwId đã tạo ở bước 2.

Quy trình gọi API được thể hiện trong các đoạn mã sau:

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);

Hành vi phát video theo yêu cầu

Vì việc phát video theo yêu cầu được tạo đường hầm được liên kết ngầm với hoạt động phát AudioTrack, nên hoạt động phát video theo yêu cầu được tạo đường hầm có thể phụ thuộc vào hành vi phát âm thanh.

  • Theo mặc định, trên hầu hết các thiết bị, khung video sẽ không được kết xuất cho đến khi quá trình phát âm thanh bắt đầu. Tuy nhiên, ứng dụng có thể cần kết xuất khung hình video trước khi bắt đầu phát âm thanh, chẳng hạn như để hiển thị cho người dùng vị trí video hiện tại trong khi tua.

    • Để báo hiệu rằng khung hình video đầu tiên trong hàng đợi sẽ được kết xuất ngay khi giải mã, hãy đặt tham số PARAMETER_KEY_TUNNEL_PEEK thành 1. Khi các khung hình video nén được sắp xếp lại trong hàng đợi (chẳng hạn như khi có khung hình B), điều này có nghĩa là khung hình video đầu tiên hiển thị phải luôn là khung hình I.

    • Nếu bạn không muốn khung video đầu tiên trong hàng đợi được kết xuất cho đến khi bắt đầu phát âm thanh, hãy đặt tham số này thành 0.

    • Nếu bạn không đặt tham số này, nhà sản xuất thiết bị gốc (OEM) sẽ xác định hành vi cho thiết bị.

  • Khi dữ liệu âm thanh không được cung cấp cho AudioTrack và vùng đệm trống (âm thanh bị thiếu), quá trình phát video sẽ bị tạm dừng cho đến khi ghi thêm dữ liệu âm thanh vì đồng hồ âm thanh không còn tiến lên nữa.

  • Trong quá trình phát, các điểm gián đoạn mà ứng dụng không thể khắc phục có thể xuất hiện trong dấu thời gian trình bày âm thanh. Khi điều này xảy ra, OEM sẽ khắc phục các khoảng trống âm bằng cách làm trì hoãn khung hình video hiện tại, cũng như các khoảng trống dương bằng cách bỏ khung hình video hoặc chèn khung âm thanh im lặng (tuỳ thuộc vào cách triển khai của OEM). Vị trí khung AudioTimestamp không tăng đối với các khung âm thanh im lặng được chèn.

Đối với nhà sản xuất thiết bị

Cấu hình

Nhà sản xuất thiết bị gốc (OEM) nên tạo một bộ giải mã video riêng để hỗ trợ phát video được chuyển qua đường hầm. Bộ giải mã này phải quảng cáo rằng nó có thể phát qua đường hầm trong tệp media_codecs.xml:

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

Khi một thực thể MediaCodec được định cấu hình bằng mã phiên âm thanh, thực thể này sẽ truy vấn AudioFlinger cho mã HW_AV_SYNC này:

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);
}

Trong truy vấn này, AudioFlinger sẽ truy xuất mã HW_AV_SYNC từ thiết bị âm thanh chính và liên kết mã này với mã phiên âm thanh trong nội bộ:

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);

Nếu bạn đã tạo một thực thể AudioTrack, thì mã HW_AV_SYNC sẽ được truyền đến luồng đầu ra có cùng mã phiên âm thanh. Nếu chưa được tạo, thì mã HW_AV_SYNC sẽ được truyền đến luồng đầu ra trong quá trình tạo AudioTrack. Việc này được thực hiện bởi luồng phát:

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

HW_AV_SYNC, cho dù tương ứng với luồng đầu ra âm thanh hay cấu hình Tuner, đều được truyền vào thành phần OMX hoặc Codec2 để mã OEM có thể liên kết bộ mã hoá và giải mã với luồng đầu ra âm thanh tương ứng hoặc luồng bộ điều chỉnh.

Trong quá trình định cấu hình thành phần, thành phần OMX hoặc Codec2 sẽ trả về một tay điều khiển băng tần bên có thể dùng để liên kết bộ mã hoá và giải mã với lớp Trình tổng hợp phần cứng (HWC). Khi ứng dụng liên kết một nền tảng với MediaCodec, trình xử lý băng tần phụ này sẽ được chuyển xuống HWC thông qua SurfaceFlinger. Thao tác này sẽ định cấu hình lớp này làm lớp băng tần phụ.

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 chịu trách nhiệm nhận vùng đệm hình ảnh mới từ đầu ra bộ mã hoá và giải mã tại thời điểm thích hợp, đồng bộ hoá với luồng đầu ra âm thanh liên quan hoặc đồng hồ tham chiếu của chương trình bộ điều chỉnh, kết hợp các vùng đệm với nội dung hiện tại của các lớp khác và hiển thị hình ảnh thu được. Việc này xảy ra độc lập với chu kỳ chuẩn bị và thiết lập thông thường. Các lệnh gọi chuẩn bị và thiết lập chỉ xảy ra khi các lớp khác thay đổi hoặc khi các thuộc tính của lớp băng tần phụ (chẳng hạn như vị trí hoặc kích thước) thay đổi.

OMX

Thành phần bộ giải mã được tạo đường hầm phải hỗ trợ những nội dung sau:

  • Đặt tham số mở rộng OMX.google.android.index.configureVideoTunnelMode. Tham số này sử dụng cấu trúc ConfigureVideoTunnelModeParams để truyền mã HW_AV_SYNC liên kết với thiết bị đầu ra âm thanh.

  • Định cấu hình tham số OMX_IndexConfigAndroidTunnelPeek để yêu cầu bộ mã hoá và giải mã hiển thị hoặc không hiển thị khung video được giải mã đầu tiên, bất kể quá trình phát âm thanh đã bắt đầu hay chưa.

  • Gửi sự kiện OMX_EventOnFirstTunnelFrameReady khi khung hình video được tạo đường hầm đầu tiên đã được giải mã và sẵn sàng hiển thị.

Phương thức triển khai AOSP định cấu hình chế độ đường hầm trong ACodec thông qua OMXNodeInstance như minh hoạ trong đoạn mã sau:

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;

Nếu thành phần hỗ trợ cấu hình này, thì thành phần đó sẽ phân bổ một tay điều khiển kênh bên cho bộ mã hoá và giải mã này và chuyển tay điều khiển đó trở lại thông qua thành phần pSidebandWindow để HWC có thể xác định bộ mã hoá và giải mã được liên kết. Nếu thành phần không hỗ trợ cấu hình này, thì thành phần đó sẽ đặt bTunneled thành OMX_FALSE.

Codec2

Trên Android 11 trở lên, Codec2 hỗ trợ tính năng phát theo đường hầm. Thành phần bộ giải mã phải hỗ trợ những nội dung sau:

  • Định cấu hình C2PortTunneledModeTuning, cấu hình chế độ đường hầm và truyền trong HW_AV_SYNC được truy xuất từ thiết bị đầu ra âm thanh hoặc cấu hình bộ thu.

  • Truy vấn C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE để phân bổ và truy xuất handle kênh bên cho HWC.

  • Xử lý C2_PARAMKEY_TUNNEL_HOLD_RENDER khi đính kèm vào C2Work. Thao tác này sẽ hướng dẫn bộ mã hoá và giải mã giải mã và báo hiệu hoàn tất công việc, nhưng không hiển thị vùng đệm đầu ra cho đến khi 1) bộ mã hoá và giải mã được hướng dẫn hiển thị vùng đệm đầu ra sau đó hoặc 2) quá trình phát âm thanh bắt đầu.

  • Xử lý C2_PARAMKEY_TUNNEL_START_RENDER, hướng dẫn bộ mã hoá và giải mã kết xuất ngay khung được đánh dấu bằng C2_PARAMKEY_TUNNEL_HOLD_RENDER, ngay cả khi chưa bắt đầu phát âm thanh.

  • Hãy để debug.stagefright.ccodec_delayed_params chưa định cấu hình (nên dùng). Nếu bạn định cấu hình, hãy đặt thành false.

Quá trình triển khai AOSP định cấu hình chế độ đường hầm trong CCodec thông qua C2PortTunnelModeTuning, như minh hoạ trong đoạn mã sau:

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;
}

Nếu thành phần hỗ trợ cấu hình này, thì thành phần đó sẽ phân bổ một tay điều khiển kênh bên cho bộ mã hoá và giải mã này và chuyển tay điều khiển đó trở lại thông qua C2PortTunnelHandlingTuning để HWC có thể xác định bộ mã hoá và giải mã được liên kết.

Audio HAL

Để phát video theo yêu cầu, HAL âm thanh nhận được dấu thời gian trình bày âm thanh cùng dòng với dữ liệu âm thanh ở định dạng lớn-endian bên trong một tiêu đề tìm thấy ở đầu mỗi khối dữ liệu âm thanh mà ứng dụng ghi:

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 hiển thị các khung video đồng bộ với các khung âm thanh tương ứng, Audio HAL phải phân tích cú pháp tiêu đề đồng bộ hoá và sử dụng dấu thời gian trình bày để đồng bộ hoá lại đồng hồ phát với quá trình kết xuất âm thanh. Để đồng bộ hoá lại khi phát âm thanh nén, Audio HAL có thể cần phân tích cú pháp siêu dữ liệu bên trong dữ liệu âm thanh nén để xác định thời lượng phát.

Tạm dừng hỗ trợ

Android 5 trở xuống không có tính năng hỗ trợ tạm dừng. Bạn chỉ có thể tạm dừng phát qua đường hầm bằng cách thiếu dữ liệu A/V, nhưng nếu bộ đệm nội bộ cho video có dung lượng lớn (ví dụ: có 1 giây dữ liệu trong thành phần OMX), thì thao tác tạm dừng sẽ không phản hồi.

Trong Android 5.1 trở lên, AudioFlinger hỗ trợ tạm dừng và tiếp tục cho đầu ra âm thanh trực tiếp (được tạo đường hầm). Nếu HAL triển khai tính năng tạm dừng và tiếp tục, thì tính năng tạm dừng và tiếp tục theo dõi sẽ được chuyển tiếp đến HAL.

Tuân thủ trình tự gọi tạm dừng, xả, tiếp tục bằng cách thực thi các lệnh gọi HAL trong luồng phát (tương tự như tính năng giảm tải).

Đề xuất triển khai

Audio HAL

Đối với Android 11, bạn có thể sử dụng mã đồng bộ hoá phần cứng từ PCR hoặc STC để đồng bộ hoá A/V, vì vậy, tính năng truyền chỉ video sẽ được hỗ trợ.

Đối với Android 10 trở xuống, các thiết bị hỗ trợ phát video được chuyển qua đường hầm phải có ít nhất một hồ sơ luồng đầu ra âm thanh với cờ FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT trong tệp audio_policy.conf. Những cờ này dùng để đặt đồng hồ hệ thống từ đồng hồ âm thanh.

OMX

Nhà sản xuất thiết bị phải có một thành phần OMX riêng để phát video được định tuyến (nhà sản xuất có thể có các thành phần OMX bổ sung cho các loại phát âm thanh và video khác, chẳng hạn như phát an toàn). Thành phần được tạo đường hầm phải:

  • Chỉ định 0 vùng đệm (nBufferCountMin, nBufferCountActual) trên cổng đầu ra.

  • Triển khai phần mở rộng OMX.google.android.index.prepareForAdaptivePlayback setParameter.

  • Chỉ định các chức năng của tính năng này trong tệp media_codecs.xml và khai báo tính năng phát qua đường hầm. Tiêu đề cũng phải nêu rõ mọi giới hạn về kích thước khung hình, căn chỉnh hoặc tốc độ bit. Sau đây là ví dụ minh hoạ:

    <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>
    

Nếu cùng một thành phần OMX được dùng để hỗ trợ giải mã qua đường hầm và không qua đường hầm, thì tính năng phát qua đường hầm sẽ không bắt buộc. Cả bộ giải mã được tạo đường hầm và không được phân lớp đều có những giới hạn về khả năng như nhau. Dưới đây là ví dụ minh hoạ:

<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>

Trình tổng hợp phần cứng (HWC)

Khi có một lớp được tạo đường hầm (một lớp có HWC_SIDEBAND compositionType) trên màn hình, sidebandStream của lớp đó là tay điều khiển băng tần bên do thành phần video OMX phân bổ.

HWC đồng bộ hoá các khung video đã giải mã (từ thành phần OMX được tạo đường hầm) với bản âm thanh được liên kết (có mã audio-hw-sync). Khi một khung hình video mới trở thành hiện tại, HWC kết hợp khung hình đó với nội dung hiện tại của tất cả các lớp nhận được trong lệnh gọi chuẩn bị hoặc đặt gần đây nhất và hiển thị hình ảnh kết quả. Lệnh gọi chuẩn bị hoặc đặt chỉ xảy ra khi các lớp khác thay đổi hoặc khi các thuộc tính của lớp băng tần bên (chẳng hạn như vị trí hoặc kích thước) thay đổi.

Hình sau đây thể hiện HWC hoạt động với bộ đồng bộ hoá phần cứng (hoặc hạt nhân hoặc trình điều khiển) để kết hợp các khung hình video (7b) với thành phần mới nhất (7a) để hiển thị vào đúng thời điểm, dựa trên âm thanh (7c).

HWC kết hợp các khung video dựa trên âm thanh

Hình 2. Trình đồng bộ hoá phần cứng (hoặc hạt nhân hoặc trình điều khiển) HWC