تونل زنی چند رسانه ای

تونل‌سازی چند رسانه‌ای، داده‌های ویدیوی فشرده را قادر می‌سازد تا از طریق رمزگشای ویدیوی سخت‌افزاری مستقیماً به صفحه نمایش تونل شوند، بدون اینکه توسط کد برنامه یا کد فریمورک اندروید پردازش شوند. کد مخصوص دستگاه در زیر پشته Android تعیین می‌کند که کدام فریم‌های ویدیویی به نمایشگر ارسال شود و چه زمانی باید آنها را با مقایسه مهرهای زمانی ارائه فریم ویدیو با یکی از انواع ساعت داخلی زیر ارسال کرد:

  • برای پخش ویدیوی درخواستی در اندروید 5 یا بالاتر، یک ساعت AudioTrack که با مُهرهای زمانی ارائه صوتی ارسال شده توسط برنامه همگام‌سازی شده است.

  • برای پخش پخش زنده در Android 11 یا بالاتر، یک ساعت مرجع برنامه (PCR) یا ساعت سیستم (STC) که توسط تیونر هدایت می شود

پس زمینه

پخش ویدیوی سنتی در اندروید، زمانی که یک فریم ویدیوی فشرده رمزگشایی شد، به برنامه اطلاع می‌دهد . سپس این برنامه فریم ویدیوی رمزگشایی شده را روی نمایشگر رها می کند تا در همان زمان ساعت سیستم با فریم صوتی مربوطه رندر شود و نمونه های تاریخی AudioTimestamps برای محاسبه زمان بندی صحیح بازیابی می کند .

از آنجایی که پخش ویدیوی تونلی شده کد برنامه را دور می زند و تعداد فرآیندهای انجام شده روی ویدیو را کاهش می دهد، بسته به اجرای OEM می تواند رندر ویدیویی کارآمدتری را ارائه دهد. همچنین می‌تواند آهنگ و همگام‌سازی دقیق‌تر ویدیویی را با ساعت انتخابی (PRC، STC، یا صدا) با اجتناب از مشکلات زمان‌بندی ناشی از انحراف احتمالی بین زمان‌بندی درخواست‌های اندروید برای رندر کردن ویدیو و زمان‌بندی سخت‌افزار واقعی و همگام‌سازی فراهم کند. با این حال، تونل‌سازی همچنین می‌تواند پشتیبانی از جلوه‌های GPU مانند تار شدن یا گوشه‌های گرد در پنجره‌های تصویر در تصویر (PiP) را کاهش دهد، زیرا بافرها پشته گرافیکی اندروید را دور می‌زنند.

نمودار زیر نشان می دهد که چگونه تونل سازی فرآیند پخش ویدیو را ساده می کند.

مقایسه حالت‌های سنت و تونل

شکل 1. مقایسه فرآیندهای پخش ویدیوی سنتی و تونلی شده

برای توسعه دهندگان برنامه

از آنجایی که اکثر توسعه دهندگان برنامه برای اجرای بازپخش با یک کتابخانه ادغام می شوند، در بیشتر موارد پیاده سازی فقط نیاز به پیکربندی مجدد آن کتابخانه برای پخش تونلی دارد. برای اجرای سطح پایین پخش کننده ویدیوی تونل شده، از دستورالعمل های زیر استفاده کنید.

برای پخش ویدیوی درخواستی در Android 5 یا بالاتر:

  1. یک نمونه SurfaceView ایجاد کنید.

  2. یک نمونه audioSessionId ایجاد کنید.

  3. نمونه های AudioTrack و MediaCodec را با نمونه audioSessionId ایجاد شده در مرحله 2 ایجاد کنید.

  4. داده های صوتی را با مهر زمانی ارائه برای اولین فریم صوتی در داده های صوتی AudioTrack قرار دهید.

برای پخش پخش زنده در اندروید 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 وجود دارند)، این بدان معناست که اولین فریم ویدئویی نمایش داده شده همیشه باید یک فریم I باشد.

    • اگر نمی‌خواهید اولین فریم ویدیویی در صف نمایش داده شود تا زمانی که پخش صدا شروع شود، این پارامتر را روی 0 تنظیم کنید.

    • اگر این پارامتر تنظیم نشود، OEM رفتار دستگاه را تعیین می کند.

  • هنگامی که داده‌های صوتی به AudioTrack ارائه نمی‌شوند و بافرها خالی هستند (کم‌تر شدن صدا)، پخش ویدیو متوقف می‌شود تا زمانی که داده‌های صوتی بیشتری نوشته شود، زیرا ساعت صوتی دیگر به جلو نمی‌رود.

  • در حین پخش، ناپیوستگی‌هایی که برنامه نتواند آنها را تصحیح کند ممکن است در مُهرهای زمانی ارائه صوتی ظاهر شوند. وقتی این اتفاق می‌افتد، OEM شکاف‌های منفی را با متوقف کردن فریم ویدیوی فعلی، و شکاف‌های مثبت را با انداختن فریم‌های ویدیو یا قرار دادن فریم‌های صوتی بی‌صدا (بسته به اجرای OEM) تصحیح می‌کند. موقعیت قاب AudioTimestamp برای فریم های صوتی بی صدا درج شده افزایش نمی یابد.

برای سازندگان دستگاه

پیکربندی

OEM ها باید یک رمزگشای ویدیویی جداگانه برای پشتیبانی از پخش ویدیوی تونلی ایجاد کنند. این رمزگشا باید اعلام کند که قادر به پخش تونلی در فایل 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 در حین ایجاد 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 مرتبط می کند، این دسته باند جانبی از طریق SurfaceFlinger به HWC منتقل می شود، که لایه را به عنوان یک لایه باند جانبی پیکربندی می کند.

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 مسئول دریافت بافرهای تصویر جدید از خروجی کدک در زمان مناسب است، یا با جریان خروجی صوتی مرتبط یا ساعت مرجع برنامه تیونر هماهنگ شده، بافرها را با محتوای فعلی لایه‌های دیگر ترکیب می‌کند و تصویر حاصل را نمایش می‌دهد. این مستقل از چرخه آماده سازی و تنظیم معمولی اتفاق می افتد. فراخوانی آماده و تنظیم تنها زمانی اتفاق می‌افتد که لایه‌های دیگر تغییر می‌کنند، یا زمانی که ویژگی‌های لایه باند جانبی (مانند موقعیت یا اندازه) تغییر می‌کنند.

OMX

یک جزء رمزگشای تونلی باید موارد زیر را پشتیبانی کند:

  • تنظیم پارامتر توسعه یافته OMX.google.android.index.configureVideoTunnelMode ، که از ساختار 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 تنظیم کنید.

کدک 2

در اندروید 11 یا بالاتر، Codec2 از پخش تونل شده پشتیبانی می کند. جزء رمزگشا باید موارد زیر را پشتیبانی کند:

  • پیکربندی 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 حالت تونل را در 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 صوتی

برای پخش ویدیوی درخواستی، Audio 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 فریم‌های ویدیو را همگام با فریم‌های صوتی مربوطه ارائه کند، Audio HAL باید هدر همگام‌سازی را تجزیه کند و از مهر زمانی ارائه برای همگام‌سازی مجدد ساعت پخش با رندر صوتی استفاده کند. برای همگام سازی مجدد هنگام پخش صدای فشرده، ممکن است Audio HAL نیاز به تجزیه فراداده در داخل داده های صوتی فشرده برای تعیین مدت زمان پخش آن داشته باشد.

توقف پشتیبانی

اندروید 5 یا پایین‌تر شامل پشتیبانی مکث نمی‌شود. می‌توانید پخش تونل‌شده را فقط با گرسنگی A/V متوقف کنید، اما اگر بافر داخلی ویدیو بزرگ باشد (مثلاً یک ثانیه داده در مؤلفه OMX وجود دارد)، باعث می‌شود که مکث پاسخگو نباشد.

در اندروید 5.1 یا بالاتر، AudioFlinger از مکث و ازسرگیری برای خروجی های صوتی مستقیم (تونل شده) پشتیبانی می کند. اگر HAL مکث و رزومه را پیاده سازی کند، مکث آهنگ و رزومه به HAL ارسال می شود.

توالی مکث، تراز، از سرگیری تماس با اجرای تماس‌های HAL در رشته پخش (همانند بارگذاری) رعایت می‌شود.

پیشنهادات اجرایی

HAL صوتی

برای Android 11، شناسه همگام‌سازی HW از PCR یا STC را می‌توان برای همگام‌سازی A/V استفاده کرد، بنابراین پخش جریانی فقط ویدیویی پشتیبانی می‌شود.

برای Android 10 یا پایین‌تر، دستگاه‌هایی که از پخش ویدیوی تونل‌شده پشتیبانی می‌کنند باید حداقل یک نمایه جریان خروجی صدا با پرچم‌های FLAG_HW_AV_SYNC و AUDIO_OUTPUT_FLAG_DIRECT در فایل audio_policy.conf داشته باشند. این پرچم ها برای تنظیم ساعت سیستم از ساعت صوتی استفاده می شود.

OMX

سازندگان دستگاه باید یک جزء OMX مجزا برای پخش ویدئوی تونلی دار داشته باشند (سازندگان می توانند اجزای 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 ) همگام‌سازی می‌کند. هنگامی که یک فریم ویدیویی جدید جاری می شود، HWC آن را با محتویات جاری تمام لایه های دریافت شده در آخرین تماس آماده یا تنظیم ترکیب می کند و تصویر حاصل را نمایش می دهد. فراخوانی آماده یا تنظیم تنها زمانی اتفاق می‌افتد که لایه‌های دیگر تغییر می‌کنند، یا زمانی که ویژگی‌های لایه باند جانبی (مانند موقعیت یا اندازه) تغییر می‌کند.

شکل زیر HWC را نشان می‌دهد که با سخت‌افزار (یا هسته یا درایور) همگام‌کننده کار می‌کند، تا فریم‌های ویدیو (7b) را با آخرین ترکیب (7a) برای نمایش در زمان صحیح، بر اساس صدا (7c) ترکیب کند.

HWC ترکیب فریم های ویدئویی بر اساس صدا

شکل 2. همگام ساز سخت افزار HWC (یا هسته یا درایور).