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 khung hình video cần gửi đến màn hình và thời điểm gửi bằng cách so sánh dấu thời gian trình bày khung hình video với một trong các loại đồng hồ nội bộ sau:

  • Đố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

Tính năng phát video truyền thống trên Android sẽ 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 tạo đường hầm ở cấp thấp, hãy làm theo các hướng dẫn sau.

Đối với tính năng 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 cùng với 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. Nhận 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ì quá trình phát video theo yêu cầu qua đường hầm được liên kết ngầm với quá trình phát AudioTrack, nên hành vi của quá trình phát video qua đường hầm có thể phụ thuộc vào hành vi của quá trình phát âm thanh.

  • Theo mặc định, trên hầu hết các thiết bị, khung hình video sẽ không được hiển thị 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ư để cho người dùng thấy vị trí video hiện tại trong khi tua.

    • Để báo hiệu rằng khung video đầu tiên trong hàng đợi sẽ hiển thị ngay sau khi được giải mã, hãy đặt tham số PARAMETER_KEY_TUNNEL_PEEK thành 1. Khi 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), tức là khung hình video hiển thị đầu tiên 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 thông số này, OEM sẽ xác định hành vi của 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 khi phát, những lần gián đoạn mà ứng dụng không khắc phục đượ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 hình 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 phải trả về một tay cầm băng tần phụ có thể dùng để liên kết bộ mã hoá và giải mã với một lớp Hardware Composer (HWC). Khi ứng dụng liên kết một nền tảng với MediaCodec, tay điều khiển kênh bên này sẽ được truyền xuống HWC thông qua SurfaceFlinger, giúp định cấu hình lớp này dưới dạng lớp kênh bên.

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 các vùng đệm hình ảnh mới từ đầu ra của bộ mã hoá và giải mã vào thời điểm thích hợp, được đồng bộ hoá với luồng đầu ra âm thanh liên kết hoặc đồng hồ tham chiếu chương trình của bộ dò, tổng hợp 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 tính năng sau:

  • Việc đặ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ã nhận dạng 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ã kết xuất hoặc không kết xuất khung video đã 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ị.

Quá trình 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 phải phân bổ xử lý băng tần phụ cho bộ mã hoá và giải mã này và truyền lại thông qua thành phần pSidebandWindow để HWC có thể xác định bộ mã hoá và giải mã liên kết. Nếu không hỗ trợ cấu hình này, thì thành phần phải đặ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 để định cấu hình chế độ đường hầm và các lệnh 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ộ dò.

  • Truy vấn C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE để phân bổ và truy xuất tay điều khiển băng tần phụ 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.

  • Không định cấu hình debug.stagefright.ccodec_delayed_params (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 hỗ trợ cấu hình này, thì thành phần nên phân bổ xử lý băng tần phụ cho bộ mã hoá và giải mã này và truyề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, HAL âm thanh 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 lại trong đường hầm do tình trạng thiếu A/V, nhưng nếu vùng đệm nội bộ cho video lớn (ví dụ: có một giây dữ liệu trong thành phần OMX), thì trạng thái tạm dừng sẽ không phản hồi.

Trên Android 5.1 trở lên, AudioFlinger hỗ trợ tính năng tạm dừng và tiếp tục đối với đầu ra âm thanh trực tiếp (được truyền âm thanh). Nếu HAL triển khai trạng thái tạm dừng và tiếp tục, thì việc tạm dừng và tiếp tục sẽ được chuyển tiếp đến HAL.

Trình tự lệnh gọi tạm dừng, xoá, tiếp tục được tuân thủ bằng cách thực thi các lệnh gọi HAL trong luồng phát (tương tự như 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ị nên có một thành phần OMX riêng để phát video theo đường hầm (nhà sản xuất có thể có thêm các thành phần OMX cho các loại nội dung phát âm thanh và video khác, chẳng hạn như phát video 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ã theo đường hầm và không theo đường hầm, thì tính năng phát theo đường hầm là không bắt buộc. Cả bộ giải mã được tạo đường hầm và không được tạo đường hầm đều có cùng giới hạn về chức năng. Sau đâ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 trong đường hầm) với bản âm thanh được liên kết (có mã nhận dạng audio-hw-sync). Khi một khung video mới trở thành khung hiện tại, HWC sẽ kết hợp khung đó 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, đồng thời hiển thị hình ảnh thu được. Lệnh gọi chuẩn bị hoặc đặt lệnh 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.

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

Hình 2. Bộ đồng bộ hoá phần cứng (hoặc nhân hệ điều hành) hoặc trình điều khiển của HWC