Uwagi programistyczne OpenSL ES

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

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). Etapem realizacji jest to logiczne miejsce, w którym można w razie potrzeby przydzielić dodatkowe zasoby.

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 zamiar ich pozyskania w przyszłości. Interfejsy są oznaczane jako implicit lub jawne. Tablica musi zawierać jawny interfejs, jeśli zostaną pozyskane później. Niejawny interfejs nie musi być uwzględniany w sekcji obiekt tworzy tablicę, ale nie ma żadnych szkód. 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 zrealizowaniu obiektu aplikacja powinna pobrać interfejsy dla każdego funkcji, których potrzebuje, używając funkcji GetInterface na początku 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. Zobacz Pobieranie z wyprzedzeniem odtwarzacza dźwięku zawiera szczegółowe informacje.

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 m.in. niemożność Połącz się z 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 usługi Object::Destroy wszystkie istniejące z interfejsami, które są danych pochodzących z powiązanego obiektu staną 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łem jest 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 między wywołaniami. 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 nieśrodowisko-czasowych, 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. Fundamenty obniżenia kosztów opóźnienie wyjścia uwzględniono najpierw w Androidzie 4.1 (poziom interfejsu API 16), w Androidzie 4.2 (poziom API 17) pojawiły się kolejne zmiany. Te ulepszenia są dostępne przez OpenSL ES do implementacji na urządzeniach, funkcji roszczenia 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. Aby potwierdzić korzystanie z OpenSL ES, sprawdź, czy używasz interfejsu API na poziomie 9 lub wyższym.
  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ź interfejs API na poziomie 17 lub wyższym, aby potwierdzić użycie 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: Za pomocą Rozmiar bufora audio aplikacja testowa do określenia rozmiaru bufora natywnego i częstotliwości próbkowania w przypadku dźwięku OpenSL ES z aplikacji na urządzeniu audio. Możesz też odwiedzić GitHuba, aby zobaczyć rozmiar bufora audio.

Liczba odtwarzaczy dźwięku z mniejszym opóźnieniem 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 zniszczyć dźwięk gdy aktywność jest wstrzymana, są to zasoby globalne udostępniane innym aplikacjom.

Aby uniknąć dźwiękowych zakłóceń, moduł obsługi wywołań zwrotnych kolejki bufora musi być wykonywany przewidywalne okno czasowe. Zwykle oznacza to brak nieograniczonego blokowania na podstawie przekierowań, warunków, operacji wejścia-wyjścia lub operacji 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 wyrenderowania następnego bufora (w przypadku odtwarzacza AudioPlayer) lub wykorzystania poprzedniego bufor (dla funkcji AudioRecord) powinien trwać mniej więcej tyle samo czasu dla 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ą przerywane, jeśli czas pracy procesora spędził w danym wywołaniu zwrotnym jest znacznie większy od średniej. 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 wprowadzania z pominięciem krótszego czasu oczekiwania. 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 gotowych ustawieniach nagrywania znajdziesz w Konfiguracja Androida interfejsu 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.

Jedną z konsekwencji potencjalnie niezależnych zegarów audio jest konieczność stosowania asynchronicznego częstotliwości próbkowania konwersji. Prosta technika (choć nie idealna dla jakości audio) umożliwiająca asynchroniczną częstotliwość próbkowania konwersji polega na duplikowaniu lub usuwaniu próbek w miarę potrzeb w pobliżu punktu przecięcia. 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 wymagań dotyczących skuteczności. Zezwala na efekty sprzętowe i programowe.
  • SL_ANDROID_PERFORMANCE_LATENCY: priorytet jest uwzględniany w czasie oczekiwania. Bez sprzętu lub efekty programowe. Jest to tryb domyślny.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: priorytet jest uwzględniany w czasie oczekiwania z uwzględnieniem efektów sprzętowych i programowych.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: priorytet nadawany oszczędzaniu energii. Zezwala na efekty sprzętowe i programowe.

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. Jedyne różnice między nimi to dostępne interfejsy 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). Zniekształcona treść wykorzystywana do wykorzystywania parsera i kodeka 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.