OpenSL ES programlama notları

Bu bölümdeki notlar, OpenSL ES 1.0.1 spesifikasyonunu tamamlayıcı niteliktedir.

Nesneler ve arayüz başlatma

OpenSL ES programlama modelinin yeni geliştiricilere yabancı olmayabilecek iki yönü, nesneler ile arayüzler arasındaki ayrım ve başlatma sırasıdır.

OpenSL ES nesneleri kısaca Java ve C++ gibi programlama dillerindeki nesne konseptine benzer. Tek fark, OpenSL ES nesnelerinin yalnızca ilişkili arayüzleri aracılığıyla görünür olmasıdır. Buna, tüm nesnelerin SLObjectItf adı verilen ilk arayüzü dahildir. Nesnenin kendisi için tanıtıcı yoktur, yalnızca nesnenin SLObjectItf arayüzünün tanıtıcısıdır.

Önce bir OpenSL ES nesnesi oluşturulur. Bu nesne SLObjectItf sonucunu döndürür, ardından gerçekleşir. Bu, ilk olarak bir nesne oluşturmanın (bellek yetersizliği veya geçersiz parametreler olması dışında hiçbir zaman başarısız olmaması dışında) ve ardından başlatma işleminin tamamlanması (kaynak yetersizliği nedeniyle başarısız olabilir) şeklinde gerçekleşen genel programlama kalıbına benzer. Gerçekleştirme adımı, uygulamaya ek kaynak tahsisi için mantıklı bir yer sağlar.

Nesne oluşturma API'sinin bir parçası olarak, uygulamalar daha sonra edinmeyi planladıkları bir dizi istenen arayüz belirtir. Bu dizinin, arayüzleri otomatik olarak edinmediğini, yalnızca gelecekte bunları elde etme niyetini gösterdiğini unutmayın. Arayüzler dolaylı veya açık olarak ayırt edilir. Açık bir arayüz, daha sonra edinilecekse dizide listelenmelidir. Örtülü bir arayüzün, nesne oluşturma dizisinde listelenmesine gerek yoktur ancak burada listelenmesinin bir zararı yoktur. OpenSL ES'nin dinamik adı verilen başka bir arayüz türü daha vardır. Bu arayüz, nesne oluşturma dizisinde belirtilmesine gerek yoktur ve nesne oluşturulduktan sonra daha sonra eklenebilir. Android uygulaması, bu karmaşıklığı önlemek için kullanışlı bir özellik sunar. Bu özellik, Nesne oluşturma sırasında dinamik arayüzler bölümünde açıklanmıştır.

Nesne oluşturulup gerçekleştirildikten sonra, uygulama ilk SLObjectItf öğesinde GetInterface öğesini kullanarak, ihtiyaç duyduğu her özellik için arayüzler edinmelidir.

Son olarak, nesne arayüzleri aracılığıyla kullanılabilir ancak bazı nesnelerin daha fazla kurulum gerektirdiğini unutmayın. Özellikle URI veri kaynağına sahip bir ses çaların bağlantı hatalarını tespit etmek için biraz daha hazırlık yapması gerekir. Ayrıntılar için Ses çaları önceden getirme bölümüne bakın.

Uygulamanız nesneyle bittikten sonra, nesneyi açıkça yok etmeniz gerekir. Aşağıdaki Yok etme bölümüne bakın.

Ses çalar önceden getirme

URI veri kaynağına sahip bir ses çalarda Object::Realize, kaynakları ayırır ancak veri kaynağına bağlanmaz (hazırlama) veya verileri önceden getirmeye başlamaz. Bunlar, oynatıcı durumu SL_PLAYSTATE_PAUSED veya SL_PLAYSTATE_PLAYING olarak ayarlandığında gerçekleşir.

Bazı bilgiler, bu dizinin sonlarına göre hâlâ bilinmiyor olabilir. Özellikle, başlangıçta Player::GetDuration, SL_TIME_UNKNOWN değerini döndürür ve MuteSolo::GetChannelCount, kanal sayısı sıfır veya hata sonucu SL_RESULT_PRECONDITIONS_VIOLATED ile başarılı bir şekilde döndürülür. Bu API'ler bilinen doğru değerleri döndürür.

Başlangıçta bilinmeyen diğer özellikler arasında (uygulama tarafından belirtilen MIME türü ve kapsayıcı türünün aksine) içeriğin başlığının incelenmesine dayalı örnek hızı ve gerçek medya içeriği türü bulunur. Bunlar daha sonra hazırlama/önceden getirme sırasında belirlenir ancak bunları alacak API yoktur.

Önceden getirme durumu arayüzü, tüm bilgilerin ne zaman kullanılabilir olduğunu algılamak için yararlıdır veya uygulamanız düzenli olarak anket yapabilir. MP3 akışının süresi gibi bazı bilgilerin hiçbir zaman bilinmeyebileceğini unutmayın.

Önceden getirme durumu arayüzü, hataların algılanmasında da yararlıdır. Bir geri çağırma kaydedin ve en azından SL_PREFETCHEVENT_FILLLEVELCHANGE ve SL_PREFETCHEVENT_STATUSCHANGE etkinliklerini etkinleştirin. Bu etkinliklerin her ikisi de aynı anda yayınlanırsa ve PrefetchStatus::GetFillLevel sıfır düzey bildiriyorsa ve PrefetchStatus::GetPrefetchStatus, SL_PREFETCHSTATUS_UNDERFLOW olarak bildiriyorsa bu, veri kaynağında kurtarılamaz bir hata olduğunu gösterir. Bu durum, yerel dosya adının olmaması veya ağ URI'sinin geçersiz olması nedeniyle veri kaynağına bağlanamamayı içerir.

OpenSL ES'nin sonraki sürümünün, veri kaynağındaki hataların ele alınması konusunda daha açık bir destek sağlaması beklenmektedir. Bununla birlikte, gelecekteki ikili program uyumluluğu için kurtarılamayan bir hatayı bildirmeye yönelik mevcut yöntemi desteklemeye devam etmeyi planlıyoruz.

Özetle, önerilen bir kod sırası şöyledir:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. SL_IID_PREFETCHSTATUS süreyle Object::GetInterface
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. SL_IID_PLAY süreyle Object::GetInterface
  8. Play::SetPlayState - SL_PLAYSTATE_PAUSED veya SL_PLAYSTATE_PLAYING

Not: Hazırlık ve önceden getirme işlemleri burada gerçekleşir. Bu süre zarfında geri arama, düzenli durum güncellemeleriyle çağrılır.

Yok etme

Uygulamanızdan çıkarken tüm nesneleri kaldırdığınızdan emin olun. Bağımlı nesneleri olan bir nesnenin yok edilmesi güvenli olmadığından, nesneler oluşturulmalarının tersi sırayla imha edilmelidir. Örneğin, şu sırayla yok edin: ses çalarlar ve kayıt cihazları, çıkış miksi ve daha sonra, son olarak motoru.

OpenSL ES, otomatik atık toplamayı veya arayüzlerin referans sayımını desteklemez. Object::Destroy çağırdıktan sonra, ilişkili nesneden türetilen tüm mevcut arayüzler tanımsız hale gelir.

Android OpenSL ES uygulaması, bu tür arayüzlerin yanlış kullanımını algılamaz. Nesne yok edildikten sonra bu tür arayüzleri kullanmaya devam ederseniz, uygulamanız kilitlenebilir veya öngörülemeyecek şekillerde davranabilir.

Nesne kaldırma sıranızın bir parçası olarak hem birincil nesne arayüzünü hem de ilişkili tüm arayüzleri açık bir şekilde NULL olarak ayarlamanızı öneririz. Bu, eski arayüz işleyicinin yanlışlıkla hatalı kullanılmasını önler.

Stereo kaydırma

Mono bir kaynağın stereo kaydırmasını etkinleştirmek için Volume::EnableStereoPosition kullanıldığında, toplam ses gücü düzeyinde 3 dB azalma olur. Bu, kaynak bir kanaldan diğerine doğru kaydırılırken toplam ses gücü seviyesinin sabit kalmasını sağlamak için gereklidir. Bu nedenle, stereo konumlandırmayı yalnızca ihtiyacınız olduğunda etkinleştirin. Daha fazla bilgi için ses kaydırma hakkındaki Wikipedia makalesine bakın.

Geri aramalar ve ileti dizileri

Geri çağırma işleyicileri, genellikle uygulama bir etkinlik algıladığında eşzamanlı olarak çağrılır. Bu nokta, uygulama açısından eşzamansız olduğundan, uygulama ile geri çağırma işleyici arasında paylaşılan tüm değişkenlere erişimi kontrol etmek için engellemeyen bir senkronizasyon mekanizması kullanmanız gerekir. Örnek kodda, örneğin arabellek sıralarında, bu senkronizasyonu atladık veya kolaylık sağlamak için senkronizasyonu engelleme özelliğini kullandık. Ancak, herhangi bir üretim kodu için gerektiği ve düzgün bir şekilde çalışmayan senkronizasyon son derece önemlidir.

Geri çağırma işleyicileri, Android çalışma zamanına bağlı olmayan dahili uygulama dışı iş parçacıklarından çağrıldığından JNI'yi kullanmak için uygun değildir. Bu dahili iş parçacıkları OpenSL ES uygulamasının bütünlüğü açısından kritik öneme sahiptir. Bu nedenle, geri çağırma işleyicinin aşırı miktarda işlem yapmasını da engellememesi veya gerçekleştirmemesi gerekir.

Geri çağırma işleyicinizin JNI kullanması veya geri çağırmayla orantılı olmayan bir iş yürütmesi gerekiyorsa işleyici bunun yerine başka bir iş parçacığının işlenmesi için bir etkinlik yayınlamalıdır. Kabul edilebilir geri çağırma iş yükü örnekleri arasında sonraki çıkış arabelleğinin oluşturulması ve sıraya alınması (AudioPlayer için), yeni doldurulmuş giriş arabelleğinin işlenmesi ve sonraki boş arabelleğin sıraya alınması (AudioRecorder için) veya Get ailesinin çoğu gibi basit API'ler verilebilir. İş yüküyle ilgili olarak aşağıdaki Performans bölümüne bakın.

Bunun tersinin güvenli olduğunu unutmayın: JNI giren bir Android uygulama iş parçacığının, engelleyenler de dahil olmak üzere OpenSL ES API'lerini doğrudan çağırmasına izin verilir. Ancak ana iş parçacığından gelen çağrıların engellenmesi, Uygulama Yanıt Vermiyor (ANR) hatasına neden olabileceğinden önerilmez.

Geri çağırma işleyicisi çağıran ileti dizisiyle ilgili karar büyük ölçüde uygulamaya bağlıdır. Bu esnekliğin nedeni, özellikle çok çekirdekli cihazlarda gelecekte optimizasyonlara izin verebilmektir.

Geri çağırma işleyicinin üzerinde çalıştığı iş parçacığının farklı çağrılarda aynı kimliğe sahip olacağı garanti edilmez. Bu nedenle, çağrılar arasında tutarlı olması için pthread_self() tarafından döndürülen pthread_t veya gettid() tarafından döndürülen pid_t bilgisine güvenmeyin. Aynı nedenle, bir geri çağırmadaki pthread_setspecific() ve pthread_getspecific() gibi iş parçacığı yerel depolama (TLS) API'lerini kullanmayın.

Uygulama, aynı nesne için aynı türde eşzamanlı geri çağırmaların gerçekleşmemesini garanti eder. Bununla birlikte, farklı iş parçacıklarında aynı nesne için farklı türde eşzamanlı geri çağırmalar yapılabilir.

Performans

OpenSL ES, yerel bir C API'si olduğundan OpenSL ES'yi çağıran çalışma zamanı olmayan uygulama iş parçacıkları, atık toplama duraklatmaları gibi çalışma zamanıyla ilgili ek yük içermez. Aşağıda açıklanan bir istisna haricinde, OpenSL ES'nin kullanımının bunun dışında ek bir performans avantajı yoktur. Özellikle, OpenSL ES'nin kullanılması, platformun genel olarak sağladığından daha düşük ses gecikmesi ve daha yüksek planlama önceliği gibi geliştirmeleri garanti etmez. Öte yandan, Android platformu ve belirli cihaz uygulamaları gelişmeye devam ettikçe OpenSL ES uygulamaları gelecekte sistem performansı iyileştirmelerinden yararlanabilir.

Bu yeniliklerden biri ses çıkışı gecikmesinin azaltılmasıdır. Daha düşük çıkış gecikmesi için temel unsurlar ilk olarak Android 4.1'de (API düzeyi 16) kullanıma sunulmuş, ardından Android 4.2'de (API düzeyi 17) ilerlemeye devam edilmiştir. Bu iyileştirmeler, android.hardware.audio.low_latency özelliğine sahip olduğunu iddia eden cihaz uygulamaları için OpenSL ES aracılığıyla kullanılabilir. Cihaz bu özelliği talep etmez ancak Android 2.3 (API düzeyi 9) veya sonraki bir sürümü destekliyorsa OpenSL ES API'lerini kullanmaya devam edebilirsiniz ancak çıkış gecikmesi daha yüksek olabilir. Düşük çıkış gecikmesi yolu, yalnızca uygulama cihazın yerel çıkış yapılandırmasıyla uyumlu bir arabellek boyutu ve örnek hızı isterse kullanılır. Bu parametreler cihaza özeldir ve aşağıda açıklandığı şekilde edinilmelidir.

Android 4.2'den (API düzeyi 17) itibaren uygulamalar, cihazın birincil çıkış akışı için platform yerel veya optimum çıkış örnek hızı ve arabellek boyutunu sorgulayabilir. Az önce bahsedilen özellik testiyle birleştirildiğinde, uygulamalar artık desteklendiğini iddia eden cihazlarda daha düşük gecikmeli çıkış için kendini uygun şekilde yapılandırabilir.

Android 4.2 (API düzeyi 17) ve önceki sürümlerde daha düşük gecikme için iki veya daha fazla arabellek sayısı gerekir. Android 4.3 (API düzeyi 18) sürümünden itibaren, daha düşük gecikme için bir arabellek sayısı yeterlidir.

Çıkış efektleri için kullanılan tüm OpenSL ES arayüzleri, düşük gecikmeli yolu engeller.

Önerilen adım sırası aşağıdaki gibidir:

  1. OpenSL ES kullanımını onaylamak için API düzeyi 9 veya üstünü kontrol edin.
  2. Aşağıdaki gibi bir kod kullanarak android.hardware.audio.low_latency özelliğini kontrol edin:

    Kotlin

    import android.content.pm.PackageManager
    ...
    val pm: PackageManager = context.packageManager
    val claimsFeature: Boolean = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)
    

    Java

    import android.content.pm.PackageManager;
    ...
    PackageManager pm = getContext().getPackageManager();
    boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
    
  3. android.media.AudioManager.getProperty() kullanımını onaylamak için API düzeyi 17 veya üstünü kontrol edin.
  4. Aşağıdaki gibi bir kodu kullanarak bu cihazın birincil çıkış akışı için yerel veya optimum çıkış örnek hızını ve arabellek boyutunu alın:

    Kotlin

    import android.media.AudioManager
    ...
    val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val sampleRate: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
    val framesPerBuffer: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
    

    Java

    import android.media.AudioManager;
    ...
    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
    String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    
    sampleRate ve framesPerBuffer değerlerinin dize olduğunu unutmayın. Öncelikle boş olup olmadığını kontrol edin, ardından Integer.parseInt() kullanarak int'e dönüştürün.
  5. Şimdi PCM arabellek sırası veri buluculu bir AudioPlayer oluşturmak için OpenSL ES'yi kullanın.

Not: Ses cihazınızdaki OpenSL ES ses uygulamalarının yerel arabellek boyutunu ve örnek hızını belirlemek için Ses Arabelleği Boyutu test uygulamasını kullanabilirsiniz. Ses-arabellek boyutu örneklerini görüntülemek için GitHub'ı da ziyaret edebilirsiniz.

Düşük gecikmeli ses çalarların sayısı sınırlıdır. Uygulamanız için birkaçdan fazla ses kaynağı gerekiyorsa sesinizi uygulama düzeyinde karıştırmayı düşünün. Etkinliğiniz duraklatıldığında ses oynatıcılarınızı yok ettiğinizden emin olun. Bunlar, diğer uygulamalarla paylaşılan küresel bir kaynaktır.

Arabellek sırası geri çağırma işleyicisi, sesli arızaları önlemek için kısa ve tahmin edilebilir bir zaman aralığı içinde yürütülmelidir. Bu genellikle karşılıklı dışlamalar, koşullar veya G/Ç işlemlerinde sınırsız engelleme gerektiği anlamına gelir. Bunun yerine kilitleri deneyin, zaman aşımları olan kilitleri ve beklemeleri ve engellemeyen algoritmaları kullanın.

Bir sonraki arabelleği oluşturmak (AudioPlayer için) veya önceki arabelleği tüketmek (AudioRecord için) için gereken hesaplama, her geri arama için yaklaşık olarak aynı uzunlukta olmalıdır. Belirsiz bir süre boyunca çalışan veya hesaplamalarında hararetli olan algoritmalardan kaçının. Herhangi bir geri aramada harcanan CPU süresi ortalamadan önemli ölçüde daha büyükse geri çağırma hesaplamasında patlama yaşanır. Özetle, ideal olan, işleyicinin CPU yürütme süresinin sıfıra yakın bir sapmaya sahip olması ve işleyicinin sınırsız süreler için engelleme yapmamasıdır.

Düşük gecikmeli ses yalnızca aşağıdaki çıkışlarda kullanılabilir:

  • Cihaz hoparlörleri.
  • Kablolu kulaklık.
  • Kablolu mikrofonlu kulaklıklar.
  • Sırayla belirtin.
  • USB dijital ses.

Bazı cihazlarda hoparlör düzeltme ve koruma için dijital sinyal işleme nedeniyle hoparlör gecikmesi diğer yollardan daha fazladır.

Android 5.0 (API Düzeyi 21) sürümünden itibaren belirli cihazlarda düşük gecikmeli ses girişi desteklenmektedir. Bu özellikten yararlanmak için öncelikle yukarıda açıklandığı gibi daha düşük gecikmeli çıkışın kullanılabildiğini onaylayın. Daha düşük gecikmeli çıkış olanağı, daha düşük gecikmeli giriş özelliği için bir ön koşuldur. Ardından, çıkış için kullanılacak örnek hızına ve arabellek boyutuna sahip bir Ses Kaydedici oluşturun. Giriş efektleri için OpenSL ES arayüzleri daha düşük gecikmeli yolu engeller. Daha düşük gecikme için kayıt hazır ayarı SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION kullanılmalıdır. Bu hazır ayar, giriş yoluna gecikme yaşayabilecek cihaza özel dijital sinyal işlemeyi devre dışı bırakır. Kayıt hazır ayarları hakkında daha fazla bilgi için yukarıdaki Android yapılandırma arayüzü bölümüne bakın.

Eşzamanlı giriş ve çıkış için her iki taraf için ayrı arabellek sırası tamamlama işleyicileri kullanılır. Her iki taraf da aynı örnek hızını kullansa bile bu geri çağırmaların göreli sırasına veya ses saatlerinin senkronizasyonuna dair herhangi bir garanti yoktur. Uygulamanız, doğru arabellek senkronizasyonu ile verileri arabelleğe almalıdır.

Bağımsız olabilecek ses saatlerinin sonuçlarından biri, eşzamansız örnek hızı dönüşümüne duyulan ihtiyaçtır. Eşzamansız örnek hızı dönüşümü için basit (ses kalitesi için ideal değildir) bir teknik, örnekleri gerektiğinde bir sıfır geçiş noktasının yakınında kopyalamak veya bırakmaktır. Daha karmaşık dönüşümler mümkündür.

Performans modları

OpenSL ES, Android 7.1'den (API Düzeyi 25) itibaren ses yolu için bir performans modu belirtmenin bir yolunu sunmuştur. Seçenekler şunlardır:

  • SL_ANDROID_PERFORMANCE_NONE: Belirli bir performans gereksinimi yoktur. Donanım ve yazılım efektlerine izin verir.
  • SL_ANDROID_PERFORMANCE_LATENCY: Gecikmeye öncelik verilir. Donanım veya yazılım efekti yoktur. Bu, varsayılan moddur.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: Gecikmeye öncelik verilir ancak donanım ve yazılım efektlerine izin verilmeye devam edilir.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: Gücü korumaya verilen öncelik. Donanım ve yazılım efektlerine izin verir.

Not: Düşük gecikmeli yol gerektirmiyorsa ve cihazın yerleşik ses efektlerinden (örneğin, video oynatmanın akustik kalitesini iyileştirmek için) yararlanmak istiyorsanız performans modunu açıkça SL_ANDROID_PERFORMANCE_NONE olarak ayarlamanız gerekir.

Performans modunu ayarlamak için Android yapılandırma arayüzünü kullanarak aşağıda gösterildiği gibi SetConfiguration öğesini çağırmanız gerekir:

  // Obtain the Android configuration interface using a previously configured SLObjectItf.
  SLAndroidConfigurationItf configItf = nullptr;
  (*objItf)->GetInterface(objItf, SL_IID_ANDROIDCONFIGURATION, &configItf);

  // Set the performance mode.
  SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE;
    result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                     &performanceMode, sizeof(performanceMode));

Güvenlik ve izinler

Kimin ne yapabileceğine bakılırsa Android'de güvenlik işlem düzeyinde yapılır. Java programlama dili kodunun yerel koddan ve Java programlama dili kodundan başka bir şey yapamaz. Aralarındaki tek fark mevcut API'lerdir.

OpenSL ES kullanan uygulamalar, yerel olmayan benzer API'ler için ihtiyaç duyacakları izinleri istemelidir. Örneğin, uygulamanız ses kaydediyorsa android.permission.RECORD_AUDIO iznine ihtiyacı vardır. Ses efektleri kullanan uygulamalar için android.permission.MODIFY_AUDIO_SETTINGS gerekir. Ağ URI kaynaklarını oynatan uygulamalar android.permission.NETWORK ürününe ihtiyaç duyar. Daha fazla bilgi için bkz. Sistem İzinleriyle Çalışma.

Platform sürümüne ve uygulamaya bağlı olarak medya içeriği ayrıştırıcılar ve yazılım codec'leri, OpenSL ES'yi çağıran Android uygulaması (donanım codec'leri soyutlanır ancak cihaza bağlıdır) bağlamında çalışabilir. Ayrıştırıcı ve codec güvenlik açıklarından yararlanmak için tasarlanan hatalı biçimlendirilmiş içerikler, bilinen bir saldırı vektörüdür. Medyayı yalnızca güvenilir kaynaklardan oynatmanızı veya güvenilir olmayan kaynaklardaki medyaları işleyen kod nispeten korumalı bir ortamda çalışacak şekilde uygulamanızı bölümlendirmenizi öneririz. Örneğin, güvenilmeyen kaynaklardan gelen medyayı ayrı bir süreçte işleyebilirsiniz. Her iki işlem de aynı UID altında çalışmaya devam etse de bu ayırma, saldırıyı daha zor hale getirir.