Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil 11 Multimedia
Pfeil 11.1 Audio
Pfeil 11.1.1 Audio aufnehmen und abspielen
Pfeil 11.1.2 Effekte
Pfeil 11.2 Sprachverarbeitung
Pfeil 11.2.1 Sprachsynthese
Pfeil 11.2.2 Spracherkennung
Pfeil 11.3 Fotos und Video
Pfeil 11.3.1 Vorhandene Funktionen nutzen
Pfeil 11.3.2 Die eigene Kamera-App
Pfeil 11.3.3 Videos drehen
Pfeil 11.4 Zusammenfassung
 
Zum Seitenanfang

11    Multimedia Zur vorigen ÜberschriftZur nächsten Überschrift

Android bietet vielfältige Möglichkeiten, Audio aufzunehmen und wiederzugeben sowie Fotos zu schießen und Videos zu drehen. Lassen Sie Ihrer Kreativität freien Lauf.

In diesem Kapitel möchte ich Sie mit den multimedialen Fähigkeiten der Plattform vertraut machen. Beispielsweise kann Android nicht nur die unterschiedlichsten Audioformate abspielen, sondern die Ausgabe auch mit akustischen Effekten versehen. Weitere spannende Aspekte sind die Umwandlung von geschriebener in gesprochene Sprache (Sprachsynthese) und Spracherkennung. Mit Android war es schon immer möglich, Fotos zu machen und Videos zu drehen. Allerdings sind die beteiligten Programmierschnittstellen nicht ganz einfach zu handhaben. Glücklicherweise hat Google mit Jetpack CameraX eine schlanke Alternative im Angebot. Beide Varianten zeige ich Ihnen im zweiten Teil dieses Kapitels.

 
Zum Seitenanfang

11.1    Audio Zur vorigen ÜberschriftZur nächsten Überschrift

Der Begriff »Diktiergerät« stammt aus einer Zeit lange vor der Smartphone-Ära. Sie sind eine ausgesprochen praktische Erfindung – einfach den Aufnahmeknopf drücken und das Mikrofon in Richtung der Tonquelle halten. Da sich an so einem Beispiel sehr schön zeigen lässt, wie Sie mit Android Audiosignale aufzeichnen und wieder abspielen können, habe ich die App RR (die beiden Buchstaben stehen für »Rasender Reporter«) entwickelt.

 
Zum Seitenanfang

11.1.1    Audio aufnehmen und abspielen Zur vorigen ÜberschriftZur nächsten Überschrift

Nach dem ersten Start sehen Sie einen fast leeren Bildschirm. Am unteren Rand befindet sich die Schaltfläche AUFNEHMEN. Ein Klick startet bzw. stoppt die Aufnahme. Haben Sie auf diese Weise ein Signal gespeichert, erscheint die korrespondierende Datei in einer Liste, wie sie in Abbildung 11.1 zu sehen ist. Um eine Aufnahme abzuspielen, klicken Sie einfach den entsprechenden Eintrag an. Beenden bricht die Wiedergabe ab. Löschen können Sie eine Aufnahme durch Antippen und Halten.

Die Benutzeroberfläche der App »RR«

Abbildung 11.1    Die Benutzeroberfläche der App »RR«

Die App besteht aus den Klassen RRActivity, RRFile und RRListAdapter. Neben dem Anzeigen der Benutzeroberfläche kümmert sich RRActivity um das Aufnehmen und Abspielen. RRListAdapter wird benötigt, um die aufgenommenen Audioschnipsel in einer Liste anzuzeigen. Hierbei unterstützt RRFile. Diese Klasse leitet von java. io.File ab und überschreibt deren Methode toString(). Der Name einer Datei ohne Erweiterung (wie Sie später noch sehen werden, lautet diese .3gp) wird als Zahl interpretiert (toLong()) und in ein java.util.Date-Objekt umgewandelt. Dieses Datum wiederum wird in Klartext als Ergebnis zurückgegeben (DateFormat.getInstance().format()).

package com.thomaskuenneth.androidbuch.rr

import android.util.Log
import java.io.File
import java.text.DateFormat
import java.util.*

const val EXT_3GP = ".3gp"
private val TAG = RRFile::class.simpleName
class RRFile(path: File?, name: String) : File(path, name) {

override fun toString(): String {
val lc = name.toLowerCase(Locale.US)
try {
val number = lc.substring(0, lc.indexOf(EXT_3GP))
val d = Date(number.toLong())
return DateFormat.getInstance().format(d)
} catch (tr: Throwable) {
Log.e(TAG, "Fehler beim Umwandeln oder Formatieren", tr)
}
return lc
}
}

Listing 11.1    Die Klasse »RRFile«

Warum sollte das Überschreiben von toString() hilfreich sein? Lassen Sie uns zum Verständnis einen Blick auf RRListAdapter werfen. Diese Klasse leitet von android.widget.ArrayAdapter ab und speichert die Daten, die von einem ListView angezeigt werden, in einem zur Laufzeit veränderbaren Feld ab. Das Aussehen eines Listenelements wird durch die Methode getView() bestimmt, die im Interface android.widget.Adapter definiert ist. Die Implementierung in der Klasse ArrayAdapter entfaltet eine Layoutdatei, die zuvor dem Konstruktor übergeben wurde. Meine Implementierung verwendet android.R.layout.simple_list_item_1. Dieses Layout besteht aus einem einzigen Element, einer TextView.

Der anzuzeigende Text ergibt sich aus dem Aufruf der Methode – Sie ahnen es sicher – toString() des mit dem Listenelement verknüpften Wertes. Die private Methode findAndAddFiles() ermittelt durch den Aufruf von listFiles alle Dateien, die in einem bestimmten Basisverzeichnis (die Funktion getBaseDir() zeige ich Ihnen gleich) liegen. Die Dateien bzw. ihre korrespondierenden File-Objekte werden mit add() einem internen Feld als RRFile-Instanzen hinzugefügt.

package com.thomaskuenneth.androidbuch.rr

import android.content.Context
import android.widget.ArrayAdapter
import java.io.File
import java.util.*

class RRListAdapter(context: Context) :
ArrayAdapter<File>(context, android.R.layout.simple_list_item_1) {

init {
findAndAddFiles()
}

private fun findAndAddFiles() {
val d: File = getBaseDir(context)
val files =
d.listFiles { dir: File?, filename: String ->
if (filename.toLowerCase(Locale.US).endsWith(EXT_3GP)) {
val f = File(dir, filename)
f.canRead() && !f.isDirectory
} else
false
}
files?.forEach {
add(RRFile(it.parentFile, it.name))
}
}
}

Listing 11.2    Die Klasse »RRListAdapter«

Meine Klasse leitet von ArrayAdapter ab. Ist Ihnen der Lambda-Ausdruck als Parameter von listFiles aufgefallen? Hier wird ein FilenameFilter übergeben. Der Rückgabewert dessen einziger Methode accept() kontrolliert, welche Elemente in das Ergebnis (File []) übernommen werden. In meinem Fall bedeutet dies: alle lesbaren Dateien (canRead()), jedoch keine Verzeichnisse (!f.isDirectory).

Aufnahme und Wiedergabe

RRActivity stellt die Benutzeroberfläche des Rasenden Reporters dar und kümmert sich um das Aufnehmen und Wiedergeben von Audiosignalen. Die Klasse kennt drei Zustände, die der Aufzählungstyp Mode durch die Werte Waiting, Recording und Playing repräsentiert. Das Programm zeichnet also entweder auf, spielt ab oder wartet auf Aktionen des Benutzers.

Der aktuelle Zustand wird in der Instanzvariablen mode abgelegt und steuert sowohl die Beschriftung als auch das Verhalten der Schaltfläche am unteren Bildschirmrand. Was beim Anklicken passiert, können Sie in der when-Abfrage im Lambda-Ausdruck des entsprechenden OnClickListener sehen. updateButtonText() setzt den Text der Schaltfläche. Um Aufnahmen erstellen zu können, muss die App die gefährliche Berechtigung android.permission.RECORD_AUDIO besitzen. Sie wird in onStart() mit checkSelfPermission() geprüft und gegebenenfalls angefordert (requestPermissions()). Nur wenn sie erteilt wurde, kann die Schaltfläche angeklickt werden.

onPause() wird immer aufgerufen, wenn die Arbeit mit einer Activity unterbrochen wird. In so einem Fall sollte die Wiedergabe oder die Aufnahme beendet werden. Meine Implementierung ruft hierzu die beiden privaten Methoden releasePlayer() und releaseRecorder() auf. Was diese tun, erkläre ich Ihnen etwas später.

package com.thomaskuenneth.androidbuch.rr

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.thomaskuenneth.androidbuch.rr.Mode.*
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.IOException

fun getBaseDir(ctx: Context): File? {
// für Zugriff auf dieses Verzeichnis sind
// ab KitKat keine Berechtigungen nötig
val dir = File(
ctx.getExternalFilesDir(null),
".RR"
)
if (!dir.mkdirs()) {
Log.d(TAG, "Verzeichnisse schon vorhanden")
}
return dir
}

private enum class Mode {
Waiting, Recording, Playing
}

private const val REQUEST_RECORD_AUDIO = 123
private val TAG = RRActivity::class.simpleName
class RRActivity : AppCompatActivity() {
private var mode = Waiting
private var currentFile: File? = null
private var player: MediaPlayer? = null
private var recorder: MediaRecorder? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listAdapter = RRListAdapter(this)
lv.adapter = listAdapter
lv.setOnItemClickListener { _, _, position, _ ->
listAdapter.getItem(position)?.let {
playAudioFile(it.absolutePath)
}
}
lv.setOnItemLongClickListener { _, _, position, _ ->
listAdapter.getItem(position)?.let {
if (it.delete()) {
listAdapter.remove(it)
}
}
true
}
b.setOnClickListener {
when (mode) {
Waiting -> {
currentFile = recordToFile()
}
Recording -> {
// die Aufnahme stoppen
recorder?.stop()
releaseRecorder()
listAdapter.add(currentFile)
currentFile = null
mode = Waiting
updateButtonText()
}
Playing -> {
player?.stop()
releasePlayer()
mode = Waiting
updateButtonText()
}
}
}
currentFile = null
mode = Waiting
player = null
recorder = null
updateButtonText()
}

override fun onStart() {
super.onStart()
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED
) {
b.isEnabled = false
requestPermissions(
arrayOf(Manifest.permission.RECORD_AUDIO),
REQUEST_RECORD_AUDIO
)
} else {
b.isEnabled = true
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_RECORD_AUDIO &&
grantResults.isNotEmpty() && grantResults[0] ==
PackageManager.PERMISSION_GRANTED
) {
b.isEnabled = true
}
}

override fun onPause() {
super.onPause()
releasePlayer()
releaseRecorder()
}

private fun updateButtonText() {
b.text = getString(if (mode != Waiting) R.string.finish
else R.string.record)
}

private fun recordToFile(): File? {
recorder = MediaRecorder()
recorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
recorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
recorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
RRFile(
getBaseDir(this), System
.currentTimeMillis().toString() + EXT_3GP
).let { f ->
try {
if (!f.createNewFile()) {
Log.d(TAG, "Datei schon vorhanden")
}
recorder?.setOutputFile(f.absolutePath)
recorder?.prepare()
recorder?.start()
mode = Recording
updateButtonText()
} catch (e: IOException) {
Log.e(TAG, "Konnte Aufnahme nicht starten", e)
}
return f
}
}

// Recorder-Ressourcen freigeben
private fun releaseRecorder() {
recorder?.release()
recorder = null
}

private fun playAudioFile(filename: String) {
player = MediaPlayer()
player?.setOnCompletionListener {
releasePlayer()
mode = Waiting
updateButtonText()
}
try {
player?.setDataSource(filename)
player?.prepare()
player?.start()
mode = Playing
updateButtonText()
} catch (thr: Exception) {
Log.e(TAG, "konnte Audio nicht wiedergeben", thr)
}
}

// Player-Ressourcen freigeben
private fun releasePlayer() {
player?.release()
player = null
}
}

Listing 11.3    Die Klasse »RRActivity«

Die Toplevel-Funktion getBaseDir() (sie gehört nicht zur Klasse RRActivity) liefert das Verzeichnis, in dem Aufnahmen abgelegt werden. Ich erzeuge auf dem primären externen Medium den Ordner .RR. Der führende Punkt des Verzeichnisnamens sorgt dafür, dass die Audiodateien des Rasenden Reporters nicht automatisch in die Android-Mediendatenbank übernommen werden. Wenn Sie eine Indizierung wünschen, entfernen Sie ihn einfach. Bei der Deinstallation der App werden alle Aufnahmen gelöscht. Der lesende und schreibende Zugriff auf Dateien unterhalb des von getExternalFilesDir() gelieferten Pfades ist seit Android 4.4 ohne spezielle Berechtigungen möglich.

[+]  Tipp

In Kapitel 9, »Dateien lesen, schreiben und drucken«, beschreibe ich, wie Sie mit En-vironment.getExternalStorageState() den Status des externen Speichermediums abfragen können. Um Ihre App möglichst robust zu machen, sollten Sie vor Zugriffen prüfen, ob Schreib- bzw. Lesevorgänge aktuell möglich sind. Ich habe dies weggelassen, um den Programmcode möglichst kompakt zu halten.

Das Anklicken eines Listenelements startet die Wiedergabe. Hierzu wird die private Methode playAudioFile() aufgerufen. Sie instanziiert zunächst ein Objekt des Typs android.media.MediaPlayer und weist es der Variablen player zu. Anschließend wird ein OnCompletionListener registriert. Der Aufruf von dessen Methode onCompletion() signalisiert das Ende eines Abspielvorgangs. In diesem Fall werden alle Ressourcen freigegeben, die durch das MediaPlayer-Objekt belegt werden.

Um eine Audiodatei wiederzugeben, müssen Sie zunächst die Quelle festlegen. Hierfür gibt es die Methode setDataSource(). Die Methode prepare() bereitet das Abspielen vor. Mit start() beginnt die Wiedergabe. Das Freigeben von Ressourcen habe ich in die Methode releasePlayer() ausgelagert. Auf diese Weise kann sehr leicht geprüft werden, ob überhaupt eine MediaPlayer-Instanz aktiv ist.

[+]  Tipp

Sie können die hier beschriebene Vorgehensweise ohne Änderungen übernehmen, wenn Sie Musikdateien abspielen möchten. Alles, was Sie hierfür benötigen, ist in der Methode playAudioFile() zu finden.

Um eine Audiodatei aufzuzeichnen, müssen Sie als Erstes ein Objekt des Typs android.media.MediaRecorder instanziieren und anschließend durch Aufruf von dessen Methode setAudioSource() die Aufnahmequelle festlegen. Das Ausgabeformat setzen Sie mit setOutputFormat(). Die Methode setAudioEncoder() bestimmt den Codec, der verwendet werden soll. Nachdem Sie den Recorder konfiguriert haben, legen Sie einen Dateinamen fest und instanziieren ein entsprechendes File-Objekt. Sie müssen die korrespondierende Datei noch anlegen, und zwar indem Sie nun die Methode createNewFile() aufrufen. prepare() bereitet die Aufnahme vor. Nach dem Aufruf von start() beginnt sie.

Um eine Aufnahme zu beenden, müssen Sie die Methode stop() Ihrer MediaRecorder-Instanz aufrufen. Anschließend sollten Sie die nicht mehr benötigten Ressourcen freigeben. Ich habe hierzu die Methode releaseRecorder() implementiert. Sie prüft analog zu releasePlayer(), ob überhaupt ein MediaRecorder-Objekt in Verwendung ist. Die Klassen MediaRecorder und MediaPlayer ermöglichen Ihnen den unkomplizierten Einstieg in die faszinierende Welt der Audioverarbeitung. Auf diesen Grundlagen aufbauend möchte ich Ihnen nun zeigen, wie man Audioeffekte einsetzt.

 
Zum Seitenanfang

11.1.2    Effekte Zur vorigen ÜberschriftZur nächsten Überschrift

Android bietet Ihnen zahlreiche Audioeffekte an. Wie Sie gleich sehen werden, lassen sich diese beliebig mischen und sowohl global als auch für einzelne Tonspuren zuschalten. Die Grundlage für meine Erläuterungen bildet die App AudioEffekteDemo. Nach dem Start des in Abbildung 11.2 gezeigten Programms spielen Sie mit Start einen kurzen Audioschnipsel ab. Sie können die Wiedergabe mit Stop anhalten. Klicken Sie abermals auf START, um den Abspielvorgang fortzusetzen.

Die App demonstriert die drei Effekte Bass Boost, Klangverbreiterung (Virtualizer) und Hall. Außer diesen kennt Android unter anderem noch EnvironmentalReverb, Equalizer und Loudness Enhancer, die Sie in beliebiger Kombination zuschalten können.

Die App »AudioEffekteDemo«

Abbildung 11.2    Die App »AudioEffekteDemo«

Allerdings lässt sich mit dem Beispiel-Sample nur Hall vernünftig demonstrieren. Um eine eigene MP3-Datei abzuspielen, kopieren Sie diese in das Verzeichnis res/raw. Dort befindet sich bereits guten_tag.mp3. Da aus dem Dateinamen eine Konstante (zum Beispiel R.raw.guten_tag) erzeugt wird, sollte dieser keine Leerzeichen enthalten und idealerweise nur aus Kleinbuchstaben, Ziffern und dem Unterstrich bestehen. Damit die App Ihre Datei abspielt, müssen Sie in der Zeile

mediaPlayer = MediaPlayer.create(this, R.raw.guten_tag)

die Konstante entsprechend ersetzen.

[»]  Hinweis

Im Verzeichnis res/raw abgelegte Dateien werden ohne Änderungen oder Konvertierungen in die Installationsdatei (.apk) einer App übernommen. Sie können über Konstanten in R.raw auf sie zugreifen. Allerdings hat die Größe solcher Dateien direkten Einfluss auf die Größe der Installationsdatei. Sie sollten dies trotz stetig schneller werdender Datenverbindungen berücksichtigen.

Die private Methode updateButtonText() setzt die Beschriftung der einzigen Schaltfläche. Ob aktuell eine Audiodatei abgespielt wird, ist in der Instanzvariablen playing hinterlegt. In der Methode onCreate() werden ein MediaPlayer-Objekt instanziiert und ein OnCompletionListener registriert. Das ist notwendig, um am Ende des Abspielvorgangs die Schaltfläche aktualisieren zu können. Die Freigabe des Players durch Aufruf von mediaPlayer.release() erfolgt aber erst, wenn die Activity zerstört wird. In diesem Fall ruft Android die Methode onDestroy() auf.

package com.thomaskuenneth.androidbuch.audioeffektedemo

import android.media.MediaPlayer
import android.media.audiofx.AudioEffect
import android.media.audiofx.BassBoost
import android.media.audiofx.PresetReverb
import android.media.audiofx.Virtualizer
import android.os.Bundle
import android.util.Log
import android.widget.CompoundButton
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

private val TAG = AudioEffekteDemoActivity::class.simpleName
class AudioEffekteDemoActivity : AppCompatActivity() {
private var mediaPlayer: MediaPlayer? = null
private var bassBoost: BassBoost? = null
private var virtualizer: Virtualizer? = null
private var reverb: PresetReverb? = null
private var playing = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// MediaPlayer instanziieren
mediaPlayer = MediaPlayer.create(this, R.raw.guten_tag)
mediaPlayer?.setOnCompletionListener {
playing = false
updateButtonText()
}
val sessionId = mediaPlayer?.audioSessionId
// BassBoost instanziieren und an Audio Session binden
bassBoost = BassBoost(0, sessionId ?: 0)
Log.d(TAG, "roundedStrength: ${bassBoost?.roundedStrength}")
if (bassBoost?.strengthSupported == true) {
bassBoost?.setStrength(1000.toShort())
}
// Checkbox schaltet BassBoost aus und ein
cbBassBoost.setOnCheckedChangeListener { _: CompoundButton?,
isChecked: Boolean ->
val result = bassBoost?.setEnabled(isChecked)
if (result != AudioEffect.SUCCESS) {
Log.e(TAG, "Bass Boost: setEnabled($isChecked) = $result")
}
}
cbBassBoost.isChecked = false
// Virtualizer instanziieren und an Audio Session binden
virtualizer = Virtualizer(0, sessionId ?: 0)
virtualizer?.setStrength(1000.toShort())
// Checkbox schaltet Virtualizer aus und ein
cbVirtualizer.setOnCheckedChangeListener { _: CompoundButton?,
isChecked: Boolean ->
val result = virtualizer?.setEnabled(isChecked)
if (result != AudioEffect.SUCCESS) {
Log.e(TAG, "Virtualizer: setEnabled($isChecked) = $result")
}
}
cbVirtualizer.isChecked = false
// Hall
reverb = PresetReverb(0, 0)
reverb?.preset = PresetReverb.PRESET_PLATE
reverb?.id?.let {
mediaPlayer?.attachAuxEffect(it)
}
mediaPlayer?.setAuxEffectSendLevel(1f)
// Checkbox schaltet Hall aus und ein
cbReverb.setOnCheckedChangeListener { _: CompoundButton?,
isChecked: Boolean ->
val result = reverb?.setEnabled(isChecked)
if (result != AudioEffect.SUCCESS) {
Log.e(TAG, "PresetReverb: setEnabled($isChecked) = $result")
}
}
cbReverb.isChecked = false
// Schaltfläche
button.setOnClickListener {
if (playing) {
mediaPlayer?.pause()
} else {
mediaPlayer?.start()
}
playing = !playing
updateButtonText()
}
playing = false
updateButtonText()
}

override fun onDestroy() {
super.onDestroy()
mediaPlayer?.stop()
bassBoost?.release()
virtualizer?.release()
reverb?.release()
mediaPlayer?.release()
}

private fun updateButtonText() {
button.text = getString(if (playing) R.string.stop
else R.string.start)
}
}

Listing 11.4    Die Klasse »AudioEffekteDemoActivity«

Die mit audioSessionId ermittelte Audiosession-ID verknüpft Audioeffekte mit einem Audiostrom. Jeder Effekt wird durch ein Ankreuzfeld aktiviert oder deaktiviert. Hierzu gibt es drei OnCheckedChangeListener.

Bass Boost und Virtualizer

Bass Boost verstärkt die niedrigen Frequenzen eines Audiostroms. android.media.audiofx.BassBoost erbt von der Basisklasse aller Audioeffekte android.media.audiofx.AudioEffect. Diese implementiert Standardverhalten, zum Beispiel das Ein- oder Ausschalten eines Effekts mit setEnabled() sowie das Freigeben von Ressourcen mit release(). Vor dem Start eines Abspielvorgangs durch mediaPlayer.start() müssen alle gewünschten Effekte mit setEnabled(true) aktiviert werden.

Nach dem Erzeugen eines BassBoost-Objekts lässt sich dessen Intensität mit setStrength() in einem Bereich zwischen 0 und 1.000 einstellen. Beachten Sie, dass der tatsächlich gesetzte Wert vom übergebenen Wert abweichen kann, wenn die Implementierung keine entsprechend feine Abstufung unterstützt. Sie können das mit roundedStrength prüfen. Mit strengthSupported können Sie herausfinden, ob die BassBoost-Implementierung das Einstellen der Intensität grundsätzlich zulässt. Ist dies nicht der Fall (der Wert ist dann false), kennt sie nur eine Stufe. Jeder Aufruf von setStrength() rundet dann auf den korrespondierenden Wert.

Die meisten Audioeffekte können entweder dem systemweiten gemixten Audiostrom oder »nur« einer bestimmten MediaPlayer-Instanz hinzugefügt werden. Dies wird über den zweiten Parameter (audioSession) des Effektkonstruktors gesteuert. 0 kennzeichnet den globalen Mix. Damit das klappt, muss eine App in ihrer Manifestdatei die Berechtigung android.permission.MODIFY_AUDIO_SETTINGS anfordern. Allerdings gilt dieses Vorgehen bei bestimmten Effekten, zum Beispiel bei Bass Boost und Virtualizer, mittlerweile als veraltet. Jeder andere Wert repräsentiert eine systemweit eindeutige AudioSession-ID, die Sie mit mediaPlayer.audioSessionId ermitteln können.

Der Effekt Virtualizer verändert die räumliche Wirkung eines Audiosignals. Wie er sich im Detail auswirkt, hängt von der Anzahl der Eingabekanäle sowie von Art und Anzahl der Ausgabekanäle ab. Eine in Stereo aufgenommene .mp3-Datei klingt über einen Kopfhörer »breiter«, wenn der Virtualizer aktiviert ist.

Hall

Je nach räumlicher Umgebung wird Schall unterschiedlich oft und stark reflektiert. Große Konzertsäle haben selbstverständlich eine andere Akustik als beispielsweise kleine Zimmer oder Kathedralen. android.media.audiofx.PresetReverb implementiert einen solchen Effekt. Anders als die beiden bereits vorgestellten Klassen Virtualizer und BassBoost wird PresetReverb immer an den globalen Mixer (Audio-Session 0) gebunden. Damit das Ausgabesignal einer MediaPlayer-Instanz eingespeist werden kann, ermitteln Sie zunächst mit reverb.id die Effekt-ID des PresetReverb-Objekts und übergeben diese an die MediaPlayer-Methode attachAuxEffect(). Anschließend setzen Sie noch den setAuxEffectSendLevel() und legen damit die Intensität fest.

Soundeffekte verleihen Musik-Apps nicht nur den nötigen Pep – sie sind schlicht ein Muss. Übrigens stellt Android einige weitere interessante Klassen im Bereich Audioverarbeitung zur Verfügung. Mit android.media.audiofx.Visualizer können Sie beispielsweise am Ausgabestrom lauschen, um diesen zu visualisieren. Denken Sie an Zeigerinstrumente früherer Hi-Fi-Tage oder an opulente Farbspiele. Experimentieren Sie mit den Programmierschnittstellen.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern