Add play-audio, a simple OpenSL ES audio player
This commit is contained in:
parent
1773070cde
commit
1c934e8a73
11
packages/play-audio/build.sh
Normal file
11
packages/play-audio/build.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
TERMUX_PKG_HOMEPAGE=http://termux.com
|
||||||
|
TERMUX_PKG_DESCRIPTION="Simple commandline audio player for Android"
|
||||||
|
TERMUX_PKG_VERSION=0.1
|
||||||
|
|
||||||
|
termux_step_make_install () {
|
||||||
|
$CXX $CFLAGS $LDFLAGS \
|
||||||
|
-std=c++14 -Wall -Wextra -pedantic -Werror \
|
||||||
|
-lOpenSLES \
|
||||||
|
$TERMUX_PKG_BUILDER_DIR/play-audio.cpp -o $TERMUX_PREFIX/bin/play-audio
|
||||||
|
cp $TERMUX_PKG_BUILDER_DIR/play-audio.1 $TERMUX_PREFIX/share/man/man1/
|
||||||
|
}
|
22
packages/play-audio/play-audio.1
Normal file
22
packages/play-audio/play-audio.1
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.Dd July 27 2015
|
||||||
|
.Dt play-audio 1
|
||||||
|
.Sh NAME
|
||||||
|
.Nm play-audio
|
||||||
|
.Nd audio player using the Android media system
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm play-audio
|
||||||
|
.Op Ar files
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm play-audio
|
||||||
|
utility plays the files listed as arguments, in order, using the Android media system.
|
||||||
|
.Pp
|
||||||
|
The supported media formats may vary across difference devices and Android versions.
|
||||||
|
.Sh EXAMPLES
|
||||||
|
Play two ogg files in succession:
|
||||||
|
.Pp
|
||||||
|
.Dl $ play-audio path/to/first.ogg path/to/second.ogg
|
||||||
|
.Pp
|
||||||
|
.Sh AUTHOR
|
||||||
|
.An Fredrik Fornwall Aq Mt fredrik@fornwall.net
|
||||||
|
|
212
packages/play-audio/play-audio.cpp
Normal file
212
packages/play-audio/play-audio.cpp
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <SLES/OpenSLES.h>
|
||||||
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
|
||||||
|
class AudioPlayer {
|
||||||
|
public:
|
||||||
|
AudioPlayer();
|
||||||
|
~AudioPlayer();
|
||||||
|
void play(char const* uri);
|
||||||
|
private:
|
||||||
|
SLObjectItf mSlEngineObject{NULL};
|
||||||
|
SLEngineItf mSlEngineInterface{NULL};
|
||||||
|
SLObjectItf mSlOutputMixObject{NULL};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MutexWithCondition {
|
||||||
|
public:
|
||||||
|
MutexWithCondition() { pthread_mutex_lock(&mutex); }
|
||||||
|
~MutexWithCondition() { pthread_mutex_unlock(&mutex); }
|
||||||
|
void waitFor() { while (!occurred) pthread_cond_wait(&condition, &mutex); }
|
||||||
|
/** From waking thread. */
|
||||||
|
void lockAndSignal() {
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
occurred = true;
|
||||||
|
pthread_cond_signal(&condition);
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
volatile bool occurred{false};
|
||||||
|
pthread_mutex_t mutex{PTHREAD_MUTEX_INITIALIZER};
|
||||||
|
pthread_cond_t condition{PTHREAD_COND_INITIALIZER};
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioPlayer::AudioPlayer() {
|
||||||
|
// "OpenSL ES for Android is designed for multi-threaded applications, and is thread-safe.
|
||||||
|
// OpenSL ES for Android supports a single engine per application, and up to 32 objects.
|
||||||
|
// Available device memory and CPU may further restrict the usable number of objects.
|
||||||
|
// slCreateEngine recognizes, but ignores, these engine options: SL_ENGINEOPTION_THREADSAFE SL_ENGINEOPTION_LOSSOFCONTROL"
|
||||||
|
SLresult result = slCreateEngine(&mSlEngineObject,
|
||||||
|
/*numOptions=*/0, /*options=*/NULL,
|
||||||
|
/*numWantedInterfaces=*/0, /*wantedInterfaces=*/NULL, /*wantedInterfacesRequired=*/NULL);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
result = (*mSlEngineObject)->Realize(mSlEngineObject, SL_BOOLEAN_FALSE);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
result = (*mSlEngineObject)->GetInterface(mSlEngineObject, SL_IID_ENGINE, &mSlEngineInterface);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
SLuint32 const numWantedInterfaces = 1;
|
||||||
|
SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ENVIRONMENTALREVERB };
|
||||||
|
SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE };
|
||||||
|
result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
result = (*mSlOutputMixObject)->Realize(mSlOutputMixObject, SL_BOOLEAN_FALSE);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensl_prefetch_callback(SLPrefetchStatusItf caller, void* pContext, SLuint32 event) {
|
||||||
|
if (event & SL_PREFETCHEVENT_STATUSCHANGE) {
|
||||||
|
SLpermille level = 0;
|
||||||
|
(*caller)->GetFillLevel(caller, &level);
|
||||||
|
if (level == 0) {
|
||||||
|
SLuint32 status;
|
||||||
|
(*caller)->GetPrefetchStatus(caller, &status);
|
||||||
|
if (status == SL_PREFETCHSTATUS_UNDERFLOW) {
|
||||||
|
// Level is 0 but we have SL_PREFETCHSTATUS_UNDERFLOW, implying an error.
|
||||||
|
printf("- ERROR: Underflow when prefetching data and fill level zero\n");
|
||||||
|
MutexWithCondition* cond = (MutexWithCondition*) pContext;
|
||||||
|
cond->lockAndSignal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensl_player_callback(SLPlayItf /*caller*/, void* pContext, SLuint32 /*event*/) {
|
||||||
|
MutexWithCondition* condition = (MutexWithCondition*) pContext;
|
||||||
|
condition->lockAndSignal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::play(char const* uri)
|
||||||
|
{
|
||||||
|
SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, (SLchar *) uri};
|
||||||
|
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
|
||||||
|
SLDataSource audioSrc = {&loc_uri, &format_mime};
|
||||||
|
|
||||||
|
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, mSlOutputMixObject};
|
||||||
|
SLDataSink audioSnk = {&loc_outmix, NULL};
|
||||||
|
|
||||||
|
// SL_IID_ANDROIDCONFIGURATION is Android specific interface, SL_IID_VOLUME is general:
|
||||||
|
SLuint32 const numWantedInterfaces = 5;
|
||||||
|
SLInterfaceID wantedInterfaces[numWantedInterfaces]{
|
||||||
|
SL_IID_ANDROIDCONFIGURATION,
|
||||||
|
SL_IID_VOLUME,
|
||||||
|
SL_IID_PREFETCHSTATUS,
|
||||||
|
SL_IID_PLAYBACKRATE,
|
||||||
|
SL_IID_EFFECTSEND
|
||||||
|
};
|
||||||
|
SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
|
||||||
|
|
||||||
|
SLObjectItf uriPlayerObject = NULL;
|
||||||
|
SLresult result = (*mSlEngineInterface)->CreateAudioPlayer(mSlEngineInterface, &uriPlayerObject, &audioSrc, &audioSnk,
|
||||||
|
numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
// Android specific interface - usage:
|
||||||
|
// SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void* pInterface);
|
||||||
|
// This function gives different interfaces. One is android-specific, from
|
||||||
|
// <SLES/OpenSLES_AndroidConfiguration.h>, done before realization:
|
||||||
|
SLAndroidConfigurationItf androidConfig;
|
||||||
|
result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_ANDROIDCONFIGURATION, &androidConfig);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
// This allows setting the stream type (default:SL_ANDROID_STREAM_MEDIA):
|
||||||
|
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
|
||||||
|
// #define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000)
|
||||||
|
/* same as android.media.AudioManager.STREAM_SYSTEM */
|
||||||
|
// #define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
|
||||||
|
/* same as android.media.AudioManager.STREAM_RING */
|
||||||
|
// #define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
|
||||||
|
/* same as android.media.AudioManager.STREAM_MUSIC */
|
||||||
|
// #define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
|
||||||
|
/* same as android.media.AudioManager.STREAM_ALARM */
|
||||||
|
// #define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004)
|
||||||
|
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
|
||||||
|
// #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
|
||||||
|
SLint32 androidStreamType = SL_ANDROID_STREAM_ALARM;
|
||||||
|
result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &androidStreamType, sizeof(SLint32));
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
// We now Realize(). Note that the android config needs to be done before, but getting the SLPrefetchStatusItf after.
|
||||||
|
result = (*uriPlayerObject)->Realize(uriPlayerObject, /*async=*/SL_BOOLEAN_FALSE);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
SLPrefetchStatusItf prefetchInterface;
|
||||||
|
result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PREFETCHSTATUS, &prefetchInterface);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
SLPlayItf uriPlayerPlay = NULL;
|
||||||
|
result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAY, &uriPlayerPlay);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
SLPlaybackRateItf playbackRateInterface;
|
||||||
|
result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAYBACKRATE, &playbackRateInterface);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
if (NULL == uriPlayerPlay) {
|
||||||
|
fprintf(stderr, "Cannot play '%s'\n", uri);
|
||||||
|
} else {
|
||||||
|
result = (*uriPlayerPlay)->SetCallbackEventsMask(uriPlayerPlay, SL_PLAYEVENT_HEADSTALLED | SL_PLAYEVENT_HEADATEND);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
MutexWithCondition condition;
|
||||||
|
result = (*uriPlayerPlay)->RegisterCallback(uriPlayerPlay, opensl_player_callback, &condition);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
result = (*prefetchInterface)->RegisterCallback(prefetchInterface, opensl_prefetch_callback, &condition);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
result = (*prefetchInterface)->SetCallbackEventsMask(prefetchInterface, SL_PREFETCHEVENT_FILLLEVELCHANGE | SL_PREFETCHEVENT_STATUSCHANGE);
|
||||||
|
|
||||||
|
// "For an audio player with URI data source, Object::Realize allocates resources but does not
|
||||||
|
// connect to the data source (i.e. "prepare") or begin pre-fetching data. These occur once the
|
||||||
|
// player state is set to either SL_PLAYSTATE_PAUSED or SL_PLAYSTATE_PLAYING."
|
||||||
|
// - http://mobilepearls.com/labs/native-android-api/ndk/docs/opensles/index.html
|
||||||
|
result = (*uriPlayerPlay)->SetPlayState(uriPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||||
|
assert(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
|
condition.waitFor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uriPlayerObject != NULL) (*uriPlayerObject)->Destroy(uriPlayerObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AudioPlayer::~AudioPlayer()
|
||||||
|
{
|
||||||
|
// "Be sure to destroy all objects on exit from your application. Objects should be destroyed in reverse order of their creation,
|
||||||
|
// as it is not safe to destroy an object that has any dependent objects. For example, destroy in this order: audio players
|
||||||
|
// and recorders, output mix, then finally the engine."
|
||||||
|
if (mSlOutputMixObject != NULL) { (*mSlOutputMixObject)->Destroy(mSlOutputMixObject); mSlOutputMixObject = NULL; }
|
||||||
|
if (mSlEngineObject != NULL) { (*mSlEngineObject)->Destroy(mSlEngineObject); mSlEngineObject = NULL; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
bool help = false;
|
||||||
|
int c;
|
||||||
|
while ((c = getopt(argc, argv, "h")) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'h': help = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (help || optind == argc) {
|
||||||
|
printf("usage: %s [files]\n", argv[0]);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioPlayer player;
|
||||||
|
for (int i = optind; i < argc; i++) player.play(argv[i]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user