Uwagi programistyczne OpenSL ES

OSTRZEŻENIE: OpenSL ES został wycofany. Deweloperzy powinni używać oprogramowania open source Biblioteka obojów dostępna na GitHubie. Oboe to kod w języku C++ zapewniający interfejs API podobny do tego AAudio Obój wywołuje AAudio, gdy AAudio jest i gdy jest niedostępna, przełącza się na OpenSL ES.

Uwagi w tej sekcji uzupełniają specyfikację OpenSL ES 1.0.1.

Inicjowanie obiektów i interfejsu

Dwa aspekty modelu programowania OpenSL ES, których nowi deweloperzy mogą nie znać, to: różnice między obiektami i interfejsami oraz sekwencja inicjowania.

Obiekt OpenSL ES jest podobny do koncepcji obiektu w języki programowania, np. Java i C++, ale obiekt OpenSL ES jest widoczny tylko przez powiązane z nim interfejsy. Obejmuje to m.in. początkowym interfejsem dla wszystkich obiektów o nazwie SLObjectItf. Nie ma nicka dla obiektu samego, tylko uchwyt do interfejsu SLObjectItf obiektu.

Najpierw tworzony jest obiekt OpenSL ES, który zwraca SLObjectItf, a potem zrealizowane. Przypomina to typowy wzorzec programistyczny dotyczący pierwszego tworzenia (który nigdy nie powinien ulegać awarii poza brakiem pamięci lub nieprawidłowymi parametrami), a następnie rozpoczynania inicjowania (co może się nie powieść z powodu braku zasobów). Etap realizacji zapewnia implementacji logiczne miejsce na przydzielenie dodatkowych zasobów w razie potrzeby.

W ramach interfejsu API do tworzenia obiektu aplikacja określa tablicę żądanych interfejsów który planuje później pozyskać. Zwróć uwagę, że ta tablica nie automatycznie pozyskać interfejsy; wskazuje jedynie na przyszły zamiar ich pozyskania. Interfejsy są oznaczane jako implicit lub jawne. Jeśli interfejs będzie używany później, musi być wymieniony w tablicy. Implikatywny interfejs nie musi być wymieniony w tablicy tworzenia obiektu , ale nic nie stoi na przeszkodzie, aby go tam umieścić. OpenSL ES ma jeszcze jeden rodzaj interfejsu o nazwie dynamic, którego nie trzeba określać w obiekcie. utwórz tablicę i można ją dodać później po utworzeniu obiektu. Wdrożenie Androida zapewnia wygodniejszą funkcję, uniknąć takiej złożoności, co opisano w Interfejsy dynamiczne podczas tworzenia obiektów.

Po utworzeniu i uruchomieniu obiektu aplikacja powinna pobrać interfejsy dla każdego potrzebne funkcje, używając GetInterface na pierwszym planie SLObjectItf.

Obiekt jest też dostępny do użycia przez interfejsy, ale pamiętaj, Niektóre obiekty wymagają przeprowadzić dodatkową konfigurację. Odtwarzacz audio ze źródłem danych URI wymaga więcej przygotowania aby wykrywać błędy połączenia. Więcej informacji znajdziesz w sekcji Wstępne pobieranie przez odtwarzacz audio.

Gdy aplikacja zakończy działanie obiektu, należy go jawnie zniszczyć. zobacz Zniszcz sekcję poniżej.

Pobieranie z wyprzedzeniem odtwarzacza audio

W przypadku odtwarzacza audio ze źródłem danych URI Object::Realize przydziela zasobów, ale nie połączyć się ze źródłem danych (przygotować) lub rozpocząć pobieranie danych z wyprzedzeniem. Pojawiają się one, gdy stan odtwarzacza jest ustawiony na SL_PLAYSTATE_PAUSED lub SL_PLAYSTATE_PLAYING.

Niektóre informacje mogą być nieznane jeszcze stosunkowo późno w tej sekwencji. W najpierw Player::GetDuration zwraca SL_TIME_UNKNOWN i MuteSolo::GetChannelCount zwraca błąd z liczbą kanałów na 0 lub parametrem wynik błędu SL_RESULT_PRECONDITIONS_VIOLATED. Te interfejsy API zwracają odpowiednie wartości gdy są już znane.

Inne właściwości, które są początkowo nieznane, to m.in. częstotliwość próbkowania i rzeczywisty typ treści multimedialnych na podstawie nagłówka treści (w przeciwieństwie do określony przez aplikację typ MIME oraz typ kontenera). Są one również określane później przygotowuj/pobierania z wyprzedzeniem, ale nie ma interfejsów API, je pobrać.

Interfejs stanu pobierania z wyprzedzeniem przydaje się do wykrywania, czy wszystkie informacje lub aplikacja może okresowo przeprowadzać sondowanie. Pamiętaj, że niektóre informacje, takie jak czas trwania strumieniowego przesyłania danych, MP3, mogą nigdy nie być znane.

Interfejs stanu pobierania z wyprzedzeniem przydaje się też do wykrywania błędów. Rejestrowanie wywołania zwrotnego i włącz co najmniej SL_PREFETCHEVENT_FILLLEVELCHANGE i SL_PREFETCHEVENT_STATUSCHANGE zdarzeń. Jeśli oba te zdarzenia są realizowane jednocześnie, PrefetchStatus::GetFillLevel zgłasza poziom zerowy oraz PrefetchStatus::GetPrefetchStatus zgłasza SL_PREFETCHSTATUS_UNDERFLOW, to wskazuje nieodwracalny błąd w źródle danych. Obejmuje to brak możliwości połączenia ze źródłem danych, ponieważ lokalna nazwa pliku nie istnieje lub identyfikator URI sieci jest nieprawidłowy.

W następnej wersji OpenSL ES planujemy dodać wyraźniejsze obsługi błędów źródła danych. Jednak dla zapewnienia zgodności z danymi binarnymi w przyszłości zamierzamy kontynuować aby obsługiwać bieżącą to metoda zgłaszania nieodwracalnego błędu.

Podsumowując, zalecana sekwencja kodu to:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. Object::GetInterface przez SL_IID_PREFETCHSTATUS
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. Object::GetInterface przez SL_IID_PLAY
  8. Play::SetPlayState do SL_PLAYSTATE_PAUSED lub SL_PLAYSTATE_PLAYING

Uwaga: Tutaj odbywa się przygotowanie i pobieranie z wyprzedzeniem. w tym czasie jest nawiązywane połączenie zwrotne z numerem okresowe aktualizacje stanu.

Zniszcz

Pamiętaj, aby zniszczyć wszystkie obiekty przy zamykaniu aplikacji. Obiekty powinny zostać zniszczone w odwrotnej kolejności, ponieważ nie jest bezpieczne zniszczenie obiektu, który obiektów. Na przykład zniszcz w tej kolejności: odtwarzacze i dyktafony, miks wyjściowy, a następnie a na koniec silnik.

OpenSL ES nie obsługuje automatycznego czyszczenia pamięci lub odwołanie i zliczania interfejsów. Po wywołaniu funkcji Object::Destroy wszystkie istniejące interfejsy wyprowadzone z powiązanego obiektu stają się niezdefiniowane.

Implementacja Android OpenSL ES nie wykrywa nieprawidłowego użycia takich interfejsów. Dalsze korzystanie z takich interfejsów po zniszczeniu obiektu może spowodować, że aplikacja ulegają awarii lub zachowują się w nieprzewidywalny sposób.

Zalecamy jawne ustawienie zarówno głównego interfejsu obiektów, jak i wszystkich powiązanych korzysta z interfejsu NULL w ramach sekwencji niszczenia obiektów, co zapobiega przypadkowemu niewłaściwe użycie nieaktualnego uchwytu interfejsu.

Panoramowanie stereo

Gdy Volume::EnableStereoPosition jest używany do włączania panoram stereo ze źródła mono, zmniejszyła się o 3 dB, moc dźwięku poziomu usługi. Jest to konieczne, aby całkowity poziom mocy dźwięku mógł pozostać na stałym poziomie źródło to przejście z jednego kanału na drugi. W związku z tym włączaj pozycjonowanie stereo tylko wtedy, gdy chcesz, . Więcej informacji znajdziesz w artykule w Wikipedii na temat: przesuwania dźwięku.

Wywołania zwrotne i wątki

Moduły obsługi wywołań zwrotnych są zwykle wywoływane synchronicznie, gdy implementacja wykrywa . Ten punkt jest asynchroniczny w odniesieniu do aplikacji, należy więc użyć funkcji nieblokującej synchronizacji do kontrolowania dostępu do dowolnych zmiennych udostępnianych między aplikacją a do obsługi wywołania zwrotnego. W przykładowym kodzie, np. w kolejkach buforów, pominęliśmy to lub używasz synchronizacji blokowania w celu uproszczenia. Pamiętaj jednak, że odpowiednie jest kluczowe dla każdego kodu produkcyjnego.

Moduły obsługi wywołań zwrotnych są wywoływane z wątków wewnętrznych niezwiązanych z aplikacją, które nie są dołączone do wywołania środowiska wykonawczego Androida, więc nie można w nich korzystać z JNI. Ponieważ wątki wewnętrzne o znaczeniu krytycznym dla integralności implementacji OpenSL ES, moduł obsługi wywołań zwrotnych również nie powinien blokować lub wykonaj nadmiernej pracy.

Jeśli moduł obsługi wywołań zwrotnych musi używać JNI lub wykonywać pracę nieproporcjonalną do wywołanie zwrotne, moduł obsługi powinien zamiast tego opublikować zdarzenie w innym wątku do przetworzenia. Przykłady dopuszczalny zbiór zadań wywołania zwrotnego obejmuje renderowanie i umieszczenie następnego bufora wyjściowego w kolejce (w przypadku odtwarzacza audio) przetwarzanie wypełnionego bufora wejściowego i dodawanie kolejnych do kolejki pusty bufor (w przypadku AudioRecorder) lub prostych interfejsów API, takich jak większość rodziny Get. Zobacz W sekcji Wydajność poniżej dotyczącą zadania.

Zwróć uwagę, że odwrotność jest bezpieczna: wątek aplikacji na Androida dodany do JNI ma prawo do bezpośrednio wywoływać interfejsy API OpenSL ES, łącznie z tymi, które blokują. Blokowanie połączeń nie polecane w wątku głównym, ponieważ mogą spowodować Aplikacja nie odpowiada (ANR).

Decyzja dotycząca wątku, który wywołuje moduł obsługi wywołania zwrotnego, należy głównie do implementacji. Dzieje się tak dlatego, że umożliwiamy przyszłe optymalizacje, zwłaszcza na urządzeniach wielordzeniowych.

Wątek, w którym działa moduł obsługi wywołania zwrotnego, nie ma takiej samej tożsamości w całym różnych połączeń. Dlatego nie polegaj na danych typu pthread_t zwróconych przez pthread_self() lub pid_t zwrócone przez gettid() na spójna w przypadku wszystkich wywołań. Z tego samego powodu nie używaj interfejsów API pamięci lokalnej TLS, takich jak pthread_setspecific() i pthread_getspecific() z oddzwonienia.

Implementacja gwarantuje, że równoczesne wywołania zwrotne tego samego rodzaju ten sam obiekt, czy nie występują. W przypadku tego samego obiektu możliwe są jednak równoczesne wywołania zwrotne różnego rodzaju w różnych wątkach.

Wydajność

OpenSL ES to natywny interfejs API C, dlatego wątki aplikacji inne niż środowisko wykonawcze, które wywołują OpenSL ES, nie mają związane ze środowiskiem wykonawczym, takie jak wstrzymania czyszczenia pamięci. Z jednym wyjątkiem opisanym poniżej Poza tym korzystanie z OpenSL ES nie wiąże się z żadną dodatkową poprawką wydajności. W szczególności Użycie OpenSL ES nie gwarantuje ulepszeń, takich jak mniejsze opóźnienie dźwięku czy większe planowanie harmonogramu w stosunku do tego, co zapewnia platforma. Z drugiej strony, Platforma Androida i różne implementacje urządzeń stale się rozwijają – aplikacja OpenSL ES mogą skorzystać na wszelkich przyszłych ulepszeniach wydajności systemu.

Jedną z takich ewolucji jest obsługa opóźnienia na wyjściu audio. Podstawy zmniejszenia opóźnienia wyjścia zostały po raz pierwszy uwzględnione w Androidzie 4.1 (interfejs API na poziomie 16), a potem w Androidzie 4.2 (interfejs API na poziomie 17) nastąpiła dalsza poprawa. Te ulepszenia są dostępne w OpenSL ES na urządzeniach, które obsługują funkcję android.hardware.audio.low_latency. Jeśli urządzenie nie zgłasza praw do tej funkcji, ale obsługuje Androida 2.3 (poziom API 9) lub nowszy, nadal możesz używać interfejsów OpenSL ES API, ale opóźnienia mogą być większe. Niższa ścieżka czasu oczekiwania wyjściowego jest używana tylko wtedy, gdy aplikacja żąda rozmiaru bufora i częstotliwości próbkowania które są zgodne z natywną konfiguracją urządzenia wyjściowego. Te parametry to na konkretnych urządzeniach, należy uzyskać w sposób opisany poniżej.

Począwszy od Androida 4.2 (poziom interfejsu API 17) aplikacja może wysyłać zapytania dotyczące natywna lub optymalna częstotliwość próbkowania wyjściowego i rozmiar bufora dla głównych danych wyjściowych urządzenia . W połączeniu z wspomnianym wcześniej testem funkcji aplikacja może się teraz konfigurować pod kątem krótszych czasów oczekiwania na urządzeniach, które go obsługują.

W przypadku Androida 4.2 (poziom interfejsu API 17) i starszych liczba buforów wynosząca co najmniej 2 jest jest wymagane w celu skrócenia czasu oczekiwania. Począwszy od Androida 4.3 (poziom interfejsu API 18), bufor jest wystarczająca do zmniejszenia opóźnienia.

Wszystkie interfejsy OpenSL ES do obsługi efektów wyjściowych nie zawierają ścieżki o mniejszym czasie oczekiwania.

Zalecana sekwencja wygląda tak:

  1. Sprawdź, czy poziom interfejsu API wynosi co najmniej 9, aby potwierdzić użycie OpenSL ES.
  2. Sprawdź funkcję android.hardware.audio.low_latency, używając takiego kodu:

    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. Sprawdź, czy interfejs API jest na poziomie 17 lub wyższym, aby potwierdzić użycie interfejsu android.media.AudioManager.getProperty().
  4. Pobierz natywną lub optymalną częstotliwość próbkowania wyjściowego i rozmiar bufora dla urządzenia podstawowe dane wyjściowe użyj takiego kodu:

    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);
    
    Pamiętaj, że sampleRate i framesPerBuffer to ciągi tekstowe. Najpierw sprawdź null, a następnie przekonwertuj na liczbę całkowitą za pomocą funkcji Integer.parseInt().
  5. Teraz w OpenSL ES utwórz odtwarzacz audio z lokalizatorem danych kolejki bufora PCM.

Uwaga: możesz użyć aplikacji testowej Rozmiar bufora audio, aby określić natywny rozmiar bufora i częstotliwość próbkowania dla aplikacji audio OpenSL ES na urządzeniu audio. Możesz też odwiedzić GitHuba, aby zobaczyć rozmiar bufora audio.

Liczba odtwarzaczy dźwięku o krótszym opóźnieniu jest ograniczona. Jeśli aplikacja wymaga więcej niż kilka źródeł dźwięku, rozważ miksowanie dźwięku na poziomie aplikacji. Pamiętaj, aby niszczyć odtwarzacze audio, gdy Twoja aktywność jest wstrzymana, ponieważ są one zasobem globalnym udostępnianym innym aplikacjom.

Aby uniknąć słyszalnych zakłóceń, wywołanie obsługi kolejki buforowej musi być wykonywane w małym i przewidywalnym oknie czasowym. Zwykle oznacza to brak nieograniczonego blokowania na semaforach, warunkach lub operacjach wejścia-wyjścia. Zamiast tego rozważ wypróbowanie blokad, blokad i oczekiwania z limitami czasu. za pomocą algorytmów nieblokujących.

Obliczenia wymagane do renderowania następnego bufora (w przypadku AudioPlayer) lub wykorzystania poprzedniego bufora (w przypadku AudioRecord) powinny zajmować mniej więcej tyle samo czasu w przypadku każdego wywołania zwrotnego. Unikaj algorytmów, które działają w niedeterministycznym czasie lub działają w i wykonywać obliczenia. Obliczenia wywołania zwrotnego są niestabilne, jeśli czas procesora poświęcony na dowolne wywołanie zwrotne jest znacznie większy niż średnia. Podsumowując, optymalny czas pracy procesora to dla modułu obsługi wariancję bliską zera, a moduł obsługi nie będzie blokował w przypadku nieograniczonych czasów.

Dźwięk z krótszym opóźnieniem jest możliwy tylko w przypadku tych wyjść:

  • Głośniki urządzenia.
  • Słuchawki przewodowe.
  • Przewodowe zestawy słuchawkowe.
  • Wytnij.
  • Cyfrowe USB audio.

Na niektórych urządzeniach opóźnienie na głośniku jest dłuższe niż w innych ścieżkach ze względu na przetwarzanie sygnałów cyfrowych korekcji i ochronie głośnika.

Od Androida 5.0 (poziom interfejsu API 21) mniejsze opóźnienia wejście audio jest obsługiwane przez wybrane urządzenia. Aby korzystać z tej funkcji, potwierdź że dostępne jest wyjście z mniejszym czasem oczekiwania, jak opisano powyżej. Możliwość zmniejszenia opóźnienia jest warunkiem wstępnym funkcji wprowadzania mniejszego opóźnienia. Następnie utwórz Dyktafon z tym samym częstotliwości próbkowania i rozmiaru bufora, jakie będą używane jako dane wyjściowe. Interfejsy OpenSL ES do efektów wejściowych wykluczają ścieżkę o mniejszym opóźnieniu. Gotowe ustawienia nagrywania Aby zmniejszyć opóźnienie, należy użyć interfejsu SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; w tym wyłącza przetwarzanie sygnałów cyfrowych konkretnego urządzenia, co może zwiększyć opóźnienie ścieżki wejściowej. Więcej informacji o wstępnie ustawionych wartościach znajdziesz w sekcji Interfejs konfiguracji Androida powyżej.

W przypadku jednoczesnego wprowadzania i wyświetlania danych stosowane są osobne moduły obsługi uzupełniania kolejki bufora z boku strony. Nie ma gwarancji względnej kolejności wywołań zwrotnych ani synchronizacji zegary audio, nawet jeśli obie strony używają tej samej częstotliwości próbkowania. Twoja aplikacja powinien buforować z prawidłową synchronizacją bufora.

Jednym z efektów potencjalnie niezależnych zegarów audio jest konieczność asynchronicznej konwersji częstotliwości próbkowania. Prostą (choć nieidealną pod względem jakości dźwięku) techniką asynchronicznej konwersji częstotliwości próbkowania jest powielanie lub pomijanie próbek w pobliżu punktu przecięcia z osią zero. Bardziej wyszukany konwersje.

Tryby wydajności

Począwszy od Androida 7.1 (API Level 25) w OpenSL ES wprowadziliśmy sposób określania trybu wydajności dla ścieżki audio. Dostępne są następujące opcje:

  • SL_ANDROID_PERFORMANCE_NONE: brak konkretnych wymagań dotyczących wyników. Umożliwia stosowanie efektów sprzętowych i programowych.
  • SL_ANDROID_PERFORMANCE_LATENCY: priorytet ma opóźnienie. Bez sprzętu lub efekty programowe. Jest to tryb domyślny.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: priorytet ma opóźnienie, ale nadal są dozwolone efekty sprzętowe i programowe.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: priorytet nadawany oszczędzaniu energii. Umożliwia stosowanie efektów sprzętowych i programowych.

Uwaga: jeśli nie potrzebujesz ścieżki o krótkim czasie oczekiwania i chcesz podjąć wykorzystać wbudowane efekty dźwiękowe (np. poprawić akustykę jakości odtwarzania filmu), musisz ustawić tryb wydajności na SL_ANDROID_PERFORMANCE_NONE

Aby ustawić tryb wydajności, musisz wywołać funkcję SetConfiguration za pomocą Androida jak pokazano poniżej:

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

Bezpieczeństwo i uprawnienia

Kto może co robić, zabezpieczenia w Androidzie są wykonywane na poziomie procesu. Programowanie w Javie nie potrafi on zrobić nic więcej niż kod natywny. Nie może on też robić nic więcej Kod w języku programowania Java. Różnią się one tylko dostępnymi interfejsami API.

Aplikacje korzystające z OpenSL ES muszą prosić o uprawnienia, których potrzebują w przypadku podobnych nienatywnych interfejsów API. Jeśli na przykład aplikacja nagrywa dźwięk, Uprawnienie android.permission.RECORD_AUDIO. Aplikacje korzystające z efektów dźwiękowych muszą mieć android.permission.MODIFY_AUDIO_SETTINGS Aplikacje odtwarzające zasoby identyfikatora URI sieci potrzebują android.permission.NETWORK. Więcej informacji: Praca z systemem Uprawnienia.

W zależności od wersji platformy i implementacji parsery treści multimedialnych kodeki mogą działać w kontekście aplikacji na Androida, która wywołuje OpenSL ES (kodeki sprzętowe są są wyodrębnione, ale zależą od urządzenia). Nieprawidłowo sformułowane treści, które mają na celu wykorzystanie luk w zabezpieczeniach parsowania i kodowania, to znany wektor ataku. Zalecamy odtwarzanie multimediów tylko z wiarygodnych źródeł partycjonować aplikację w taki sposób, by kod obsługujący multimedia niewiarygodne źródła działają we stosunkowo piaskownicy. Możesz na przykład: przetwarzać multimedia z niezaufanych źródeł w osobnym procesie. Chociaż oba te procesy korzystające z tego samego identyfikatora UID, takie rozdzielenie utrudnia przeprowadzanie ataku.