OpenSL ES programlama notları

UYARI: OpenSL ES desteği sonlandırıldı. Geliştiriciler, GitHub'da bulunan açık kaynak Oboe kitaplığını kullanmalıdır. Oboe, AAudio'ya çok benzeyen bir API sağlayan bir C++ sarmalayıcısıdır. Oboe, AAudio kullanılabilir olduğunda AAudio'yu çağırır ve AAudio kullanılamıyorsa OpenSL ES'ye geri döner.

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ı gelebilecek iki yönü, nesneler ile arayüzler arasındaki fark ve başlatma sırasıdır.

Özet olarak, OpenSL ES nesnesi Java ve C++ gibi programlama dillerindeki nesne kavramına benzer. Bununla birlikte, OpenSL ES nesnesi yalnızca ilişkili arayüzleri aracılığıyla görünür. Buna, tüm nesnelerin ilk arayüzü (SLObjectItf) dahildir. Nesnelerin kendisi için tutma yeri yoktur. Yalnızca nesnenin SLObjectItf arayüzü için bir tutma yeri olur.

İlk olarak bir OpenSL ES nesnesi oluşturulur. Bu işlem, SLObjectItf ve ardından gerçekleştirilmiş değeri döndürür. Bu, önce bir nesne oluşturma (bellek veya geçersiz parametreler dışında hiçbir zaman başarısız olmamalıdır) ve ardından ilklendirmeyi tamamlama (kaynak eksikliği nedeniyle başarısız olabilir) şeklindeki yaygın programlama kalıbına benzer. Gerçekleştirme adımı, gerekirse uygulamaya ek kaynaklar ayırmak için mantıklı bir yer sağlar.

Nesne oluşturma API'sinin bir parçası olarak, uygulama daha sonra edinmeyi planladığı bir dizi istenen arayüz belirtir. Bu dizinin arayüzleri otomatik olarak edinmediğini, yalnızca bunları edinme niyetini belirttiğini unutmayın. Arayüzler örtülü 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 listelenmemesi gerekir ancak burada listelenmesinde bir sakınca yoktur. OpenSL ES'de dinamik adlı bir arayüz daha vardır. Bu arayüzün, nesne oluşturma dizisinde belirtilmesi gerekmez ve nesne oluşturulduktan sonra eklenebilir. Android uygulaması, bu karmaşıklıktan kaçınmak için Nesne oluşturma sırasında dinamik arayüzler bölümünde açıklanan bir kolaylık özelliği sağlar.

Nesne oluşturulduktan ve gerçekleştirildikten sonra, uygulama, ilk SLObjectItf üzerinde GetInterface 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ı algılamak için biraz daha fazla hazırlık yapması gerekir. Ayrıntılar için Ses çalar ön getirme bölümüne bakın.

Uygulamanız nesneyle tamamlandıktan sonra onu açıkça yok etmeniz gerekir. Aşağıdaki Kaldır bölümüne bakın.

Ses çalar ön getirme

URI veri kaynağına sahip bir ses oynatıcı için Object::Realize, kaynakları ayırır ancak veri kaynağına bağlanmaz (hazırlanmaz) veya verileri önceden getirmeye başlamaz. Bu olaylar, oynatıcı durumu SL_PLAYSTATE_PAUSED veya SL_PLAYSTATE_PLAYING olarak ayarlandıktan sonra gerçekleşir.

Bazı bilgiler, bu dizinin nispeten sonlarına kadar bilinmemeye devam edebilir. Özellikle, başlangıçta Player::GetDuration SL_TIME_UNKNOWN döndürür ve MuteSolo::GetChannelCount, kanal sayısı sıfır olarak başarıyla döndürülür veya SL_RESULT_PRECONDITIONS_VIOLATED hata sonucunu döndürür. Bu API'ler, bilindikten sonra uygun değerleri döndürür.

Başlangıçta bilinmeyen diğer özellikler arasında, içeriğin üstbilgisinin incelenmesine dayalı örnekleme hızı ve gerçek medya içeriği türü (uygulama tarafından belirtilen MIME türü ve kapsayıcı türü yerine) yer alır. Bunlar, hazırlama/ön getirme sırasında da belirlenir ancak bunları almak için API yoktur.

Önceden getirme durumu arayüzü, tüm bilgilerin ne zaman mevcut olduğunu veya uygulamanızın düzenli olarak yoklama yapabileceğini algılamak için yararlıdır. MP3'lerin oynatma süresi gibi bazı bilgilerin asla bilinemeyeceğini unutmayın.

Önceden getirme durumu arayüzü, hataların algılanmasında da yararlıdır. Geri çağırma işlevi kaydedin ve en az SL_PREFETCHEVENT_FILLLEVELCHANGE ve SL_PREFETCHEVENT_STATUSCHANGE etkinliklerini etkinleştirin. Bu etkinliklerin her ikisi de aynı anda yayınlanıyorsa ve PrefetchStatus::GetFillLevel sıfır düzeyi, PrefetchStatus::GetPrefetchStatus ise SL_PREFETCHSTATUS_UNDERFLOW değerini raporluyorsa bu, veri kaynağında kurtarılamaz bir hata olduğunu gösterir. Yerel dosya adı mevcut olmadığı veya ağ URI'si geçersiz olduğu için veri kaynağına bağlanamama da buna dahildir.

OpenSL ES'nin bir sonraki sürümünün, veri kaynağındaki hataların işlenmesi için daha açık destek sağlaması beklenir. Ancak gelecekteki ikili uyumluluk için, kurtarılamaz hataları bildirmek üzere mevcut yöntemi desteklemeye devam etmeyi planlıyoruz.

Özet olarak, önerilen kod sırası şu şekildedir:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. SL_IID_PREFETCHSTATUS için Object::GetInterface
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. SL_IID_PLAY için Object::GetInterface
  8. Play::SetPlayState ila SL_PLAYSTATE_PAUSED veya SL_PLAYSTATE_PLAYING

Not: hazırlık ve ön getirme işlemi burada gerçekleşir. Bu süre zarfında geri çağırma işleviniz, dönemsel durum güncellemeleriyle çağrılır.

Yok Et

Uygulamanızdan çıkarken tüm nesneleri yok ettiğinizden emin olun. Bağımlı nesneleri olan bir nesnenin kaldırılması güvenli olmadığından, nesneler oluşturuldukları sıranın tersine göre kaldırılmalıdır. Örneğin, ses oynatıcıları ve kaydediciler, çıkış karışımı ve ardından motoru şu sırayla yok edin.

OpenSL ES, arayüzlerin otomatik çöp toplama veya referans sayımını desteklemez. Object::Destroy çağrıldıktan sonra, ilişkili nesneden türetilen mevcut tüm arayüzler tanımlanmaz.

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 etmek, uygulamanızın çökmesine veya öngörülemeyen şekillerde davranmasına neden olabilir.

Nesne imha sıranız kapsamında hem birincil nesne arayüzünü hem de ilişkili tüm arayüzleri NULL olarak açıkça ayarlamanızı öneririz. Bu, eski bir arayüz mülkünün yanlışlıkla kötüye 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ü seviyesinde 3 dB'lik bir düşüş olur. Bu, kaynak bir kanaldan diğerine 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 varsa etkinleştirin. Daha fazla bilgi için ses panlaması ile ilgili Wikipedia makalesine bakın.

Geri çağırma işlevleri ve mesaj dizileri

Geri çağırma işleyicileri genellikle uygulama bir etkinlik algıladığında eşzamanlı olarak çağrılır. Bu nokta, uygulamaya göre asenkron olduğundan uygulama ile geri çağırma işleyicisi arasında paylaşılan tüm değişkenlere erişimi kontrol etmek için engellenmeyen bir senkronizasyon mekanizması kullanmanız gerekir. Örnek kodda (örneğin, arabellek sıraları için) ya bu senkronizasyonu atladık ya da işlemi basitleştirmek adına engelleme senkronizasyonunu kullandık. Bununla birlikte, engellemeyen düzgün senkronizasyon, tüm üretim kodları için kritik öneme sahiptir.

Geri çağırma işleyicileri, Android çalışma zamanına bağlı olmayan dahili uygulama dışı iş parçacıklarından çağrılır. Bu nedenle, JNI'yi kullanamazlar. Bu dahili iş parçacıkları, OpenSL ES uygulamasının bütünlüğü için kritik olduğundan geri çağırma işleyicisi de engelleme yapmamalı veya aşırı çalışma gerçekleştirmemelidir.

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

Bunun tam tersinin güvenli olduğunu unutmayın: JNI'ye 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 Uygulama Yanıt Vermiyor (ANR) durumuna neden olabileceğinden, ana iş parçacığında engelleme çağrıları önerilmez.

Geri çağırma işleyicisini çağıran ileti dizisi büyük ölçüde uygulamaya bırakılır. Bu esnekliğin nedeni, özellikle çok çekirdekli cihazlarda gelecekteki optimizasyonlara izin vermektir.

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

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

Performans

OpenSL ES doğal bir C API'si olduğundan, OpenSL ES'i çağıran çalışma zamanı dışındaki uygulama iş parçacıklarında, çalışma zamanı ile ilgili ek yük (ör. çöp toplama duraklamaları) yoktur. Aşağıda açıklanan bir istisna dışında OpenSL ES kullanımıyla ilgili ek performans avantajı yoktur. Özellikle OpenSL ES'nin kullanılması, daha düşük ses gecikmesi ve platformun genel olarak sağladığına göre daha yüksek programlama önceliği gibi geliştirmeleri garanti etmez. Öte yandan, Android platformu ve belirli cihaz uygulamaları gelişmeye devam ettikçe OpenSL ES uygulaması, gelecekteki sistem performansı iyileştirmelerinden yararlanabilir.

Bu gelişmelerden biri de ses çıkışı gecikmesinin azaltılmasıdır. Çıkış gecikmesinin azaltılmasına yönelik temel bilgiler ilk olarak Android 4.1'e (API düzeyi 16) dahil edildi ve ardından Android 4.2'de (API düzeyi 17) bu konuda ilerleme kaydedildi. Bu iyileştirmeler, android.hardware.audio.low_latency özelliği için hak talebinde bulunan cihaz uygulamaları için OpenSL ES üzerinden kullanılabilir. Cihaz bu özelliği kullanmıyor ancak Android 2.3 (API düzeyi 9) veya sonraki sürümleri destekliyorsa OpenSL ES API'lerini kullanmaya devam edebilirsiniz ancak çıkış gecikmesi daha yüksek olabilir. Daha düşük çıkış gecikmesi yolu yalnızca uygulama, cihazın yerel çıkış yapılandırmasıyla uyumlu bir arabellek boyutu ve örnekleme hızı istediğinde kullanılır. Bu parametreler cihaza özeldir ve aşağıda açıklandığı şekilde alınmalıdır.

Android 4.2'den (API düzeyi 17) itibaren uygulamalar, cihazın birincil çıkış akışı için platformun yerel veya optimum çıkış örnekleme hızını ve arabellek boyutunu sorgulayabilir. Uygulamalar, daha önce bahsedilen özellik testiyle birlikte artık destek olduğunu iddia eden cihazlarda daha düşük gecikme süresi çıkışı için kendisini 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 tüm OpenSL ES arayüzleri, düşük gecikmeli yolu engeller.

Önerilen sıra şu şekildedir:

  1. OpenSL ES'in kullanıldığını onaylamak için API seviyesi 9 veya daha yüksek bir sürüm olup olmadığı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 sonraki sürümleri kontrol edin.
  4. Aşağıdaki gibi bir kod kullanarak bu cihazın birincil çıkış akışıyla ilgili doğal veya optimal çıkış örnekleme hızını ve arabelleği 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 değerin boş olup olmadığını kontrol edin, ardından Integer.parseInt() kullanarak int değerine dönüştürün.
  5. Artık PCM arabellek sırası veri bulucu içeren bir AudioPlayer oluşturmak için OpenSL ES'i kullanın.

Not: Ses cihazınızdaki OpenSL ES ses uygulamaları için doğal arabellek boyutunu ve örnekleme hızını belirlemek üzere Ses Arabellek Boyutu test uygulamasını kullanabilirsiniz. audio-buffer-size örneklerini görüntülemek için GitHub'ı da ziyaret edebilirsiniz.

Düşük gecikmeli ses oynatıcıların sayısı sınırlıdır. Uygulamanız birden fazla ses kaynağı gerektiriyorsa uygulama düzeyinde ses mikslemeyi değerlendirin. Diğer uygulamalarla paylaşılan global bir kaynak olduklarından, etkinliğiniz duraklatıldığında ses oynatıcılarınızı yok ettiğinizden emin olun.

Sesli aksaklıkları önlemek için arabellek sırası geri çağırma işleyicisinin küçük ve tahmin edilebilir bir zaman aralığında çalıştırılması gerekir. Bu, genellikle kilitleme, koşul veya G/Ç işlemlerinde sınırsız engelleme olmadığı anlamına gelir. Bunun yerine try locks, kilit ve zaman aşımı ile bekleme ve engellemeyen algoritmaları kullanabilirsiniz.

Sonraki arabelleği oluşturmak (AudioPlayer için) veya önceki arabelleği tüketmek (AudioRecord için) için gereken hesaplama, her geri çağırma için yaklaşık olarak aynı süreyi almalıdır. Belirsiz bir sürede çalışan veya hesaplamalarında ani artışlar olan algoritmalardan kaçının. Herhangi bir geri çağırma işleminde harcanan CPU süresi ortalamadan önemli ölçüde daha fazlaysa geri çağırma hesaplaması patlama yapar. Özet olarak, işleyicinin CPU yürütme süresinin sıfıra yakın bir varyansa sahip olması ve işleyicinin sınırsız süre boyunca engellememesi idealdir.

Yalnızca şu çıkışlarda ses daha düşük gecikmeli olabilir:

  • Cihaz hoparlörleri.
  • Kablolu kulaklık.
  • Kablolu kulaklıklar.
  • Hat çıkışı.
  • 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 yüksektir.

Android 5.0 (API düzeyi 21) itibarıyla, belirli cihazlarda daha düşük gecikmeli ses girişi desteklenir. Bu özellikten yararlanmak için önce yukarıda açıklandığı gibi düşük gecikmeli çıkışın kullanılabildiğini onaylayın. Düşük gecikmeli çıkış özelliği, düşük gecikmeli giriş özelliğinin ön koşuludur. Ardından, çıkış için kullanılacak örnek hızına ve arabellek boyutuna sahip bir AudioRecorder oluşturun. Giriş efektleri için OpenSL ES arayüzleri, 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 ekleyebilecek cihaza özgü 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 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 garanti verilmez. Uygulamanız, verileri uygun arabellek senkronizasyonu ile arabelleğe almalıdır.

Bağımsız olabilecek ses saatlerinden biri, zaman uyumsuz örnekleme hızı dönüşümü ihtiyacıdır. Asenkron örnek hızı dönüşümü için basit (ses kalitesi açısından ideal olmasa da) bir teknik, sıfır geçiş noktasının yakınında gerektiği gibi örnekleri kopyalamak veya silmek olacaktır. Daha karmaşık dönüşümler mümkündür.

Performans modları

Android 7.1 (API düzeyi 25) sürümünden itibaren OpenSL ES, ses yolu için performans modu belirtme yöntemini kullanıma sundu. Seçenekler şunlardır:

  • SL_ANDROID_PERFORMANCE_NONE: Belirli bir performans şartı yoktur. Donanım ve yazılım efektlerine izin verir.
  • SL_ANDROID_PERFORMANCE_LATENCY: Gecikmeye öncelik verilir. Donanım veya yazılım efektleri Bu, varsayılan moddur.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: Donanım ve yazılım efektlerine izin vermeye devam ederken gecikmeye öncelik verilir.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: Güç tasarrufuna öncelik verilir. Donanım ve yazılım efektlerine izin verir.

Not: Düşük gecikmeli bir yola ihtiyacınız yoksa ve cihazın yerleşik ses efektlerinden yararlanmak istiyorsanız (ör. video oynatma için akustik kalitesini artırmak amacıyla) performans modunu açıkça SL_ANDROID_PERFORMANCE_NONE olarak ayarlamanız gerekir.

Performans modunu ayarlamak için aşağıda gösterildiği gibi Android yapılandırma arayüzünü kullanarak SetConfiguration'ü ç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

Android'de güvenlik, kimlerin ne yapabileceğine göre işlem düzeyinde sağlanır. Java programlama dili kodu, yerel koddan daha fazlasını yapamaz ve yerel kod da Java programlama dili kodundan daha fazlasını yapamaz. Aralarındaki tek fark, kullanılabilen 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ın android.permission.MODIFY_AUDIO_SETTINGS olması gerekir. Ağ URI kaynaklarını oynatan uygulamaların android.permission.NETWORK'e ihtiyacı vardır. Daha fazla bilgi için Sistem İzinleriyle Çalışma bölümünü inceleyin.

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ı bağlamında çalışabilir (donanım codec'leri soyuttur ancak cihaza bağlıdır). Ayrıştırıcı ve codec güvenlik açıklarından yararlanmak için tasarlanmış hatalı biçimlendirilmiş içerik, bilinen bir saldırı vektörüdür. Medyayı yalnızca güvenilir kaynaklardan oynatmanızı veya uygulamanızı, güvenilir olmayan kaynaklardan gelen medyayı işleyen kod nispeten korumalı bir ortamda çalışacak şekilde bölümlendirmenizi öneririz. Örneğin, güvenilir olmayan kaynaklardan gelen medyayı ayrı bir işlemde 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.