Fredrik Fornwall
8 years ago
3 changed files with 10 additions and 275 deletions
@ -1,27 +1,22 @@ |
|||||
TERMUX_PKG_HOMEPAGE=http://termux.com |
TERMUX_PKG_HOMEPAGE=https://github.com/termux/play-audio |
||||
TERMUX_PKG_DESCRIPTION="Simple commandline audio player for Android" |
TERMUX_PKG_DESCRIPTION="Simple commandline audio player for Android" |
||||
TERMUX_PKG_VERSION=0.4 |
TERMUX_PKG_VERSION=0.4 |
||||
|
TERMUX_PKG_SRCURL=https://github.com/termux/play-audio/archive/v${TERMUX_PKG_VERSION}.tar.gz |
||||
|
TERMUX_PKG_SHA256=95d495d2692b4ac13b5d0c9f680410f0c08e563ea67ae8de0089c7d9366fa223 |
||||
|
TERMUX_PKG_FOLDERNAME=play-audio-$TERMUX_PKG_VERSION |
||||
|
TERMUX_PKG_BUILD_IN_SRC=yes |
||||
|
|
||||
termux_step_make_install () { |
termux_step_post_make_install () { |
||||
local LIBEXEC_BINARY=$TERMUX_PREFIX/libexec/play-audio |
local LIBEXEC_BINARY=$TERMUX_PREFIX/libexec/play-audio |
||||
# Use $CC instead of $CXX to link in order to avoid linking |
local BIN_BINARY=$TERMUX_PREFIX/bin/play-audio |
||||
# against libgnustl_shared.so, since the launcher script |
mv $BIN_BINARY $LIBEXEC_BINARY |
||||
# below removes LD_LIBRARY_PATH. |
|
||||
$CC $CFLAGS $LDFLAGS \ |
|
||||
-std=c++14 -Wall -Wextra -pedantic -Werror \ |
|
||||
-fno-exceptions \ |
|
||||
-lOpenSLES \ |
|
||||
$TERMUX_PKG_BUILDER_DIR/play-audio.cpp -o $LIBEXEC_BINARY |
|
||||
|
|
||||
cat << EOF > $TERMUX_PREFIX/bin/play-audio |
cat << EOF > $BIN_BINARY |
||||
#!/bin/sh |
#!/bin/sh |
||||
|
|
||||
# Avoid linker errors due to libOpenSLES.so: |
# Avoid linker errors due to libOpenSLES.so: |
||||
LD_LIBRARY_PATH= exec $LIBEXEC_BINARY "\$@" |
LD_LIBRARY_PATH= exec $LIBEXEC_BINARY "\$@" |
||||
EOF |
EOF |
||||
|
|
||||
chmod +x $TERMUX_PREFIX/bin/play-audio |
chmod +x $BIN_BINARY |
||||
|
|
||||
mkdir -p $TERMUX_PREFIX/share/man/man1/ |
|
||||
cp $TERMUX_PKG_BUILDER_DIR/play-audio.1 $TERMUX_PREFIX/share/man/man1/ |
|
||||
} |
} |
||||
|
@ -1,25 +0,0 @@ |
|||||
.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 Fl s Ar stream |
|
||||
.Op Ar files |
|
||||
.Sh DESCRIPTION |
|
||||
The |
|
||||
.Nm play-audio |
|
||||
utility plays one or more files listed as arguments using the Android media system. |
|
||||
.Pp |
|
||||
The supported media formats may vary across difference devices and Android versions. |
|
||||
.Pp |
|
||||
The audio stream type (which affects the volume) may be specified as 'alarm', 'media' (default), 'notification', 'ring', 'system' or 'voice'. |
|
||||
.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 |
|
||||
|
|
@ -1,235 +0,0 @@ |
|||||
#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); |
|
||||
/**
|
|
||||
* This allows setting the stream type (default:SL_ANDROID_STREAM_MEDIA): |
|
||||
* SL_ANDROID_STREAM_ALARM - same as android.media.AudioManager.STREAM_ALARM |
|
||||
* SL_ANDROID_STREAM_MEDIA - same as android.media.AudioManager.STREAM_MUSIC |
|
||||
* SL_ANDROID_STREAM_NOTIFICATION - same as android.media.AudioManager.STREAM_NOTIFICATION |
|
||||
* SL_ANDROID_STREAM_RING - same as android.media.AudioManager.STREAM_RING |
|
||||
* SL_ANDROID_STREAM_SYSTEM - same as android.media.AudioManager.STREAM_SYSTEM |
|
||||
* SL_ANDROID_STREAM_VOICE - same as android.media.AudioManager.STREAM_VOICE_CALL |
|
||||
*/ |
|
||||
void setStreamType(SLint32 streamType) { this->androidStreamType = streamType; } |
|
||||
private: |
|
||||
SLObjectItf mSlEngineObject{NULL}; |
|
||||
SLEngineItf mSlEngineInterface{NULL}; |
|
||||
SLObjectItf mSlOutputMixObject{NULL}; |
|
||||
SLint32 androidStreamType{SL_ANDROID_STREAM_MEDIA}; |
|
||||
}; |
|
||||
|
|
||||
class MutexWithCondition { |
|
||||
public: |
|
||||
MutexWithCondition() { |
|
||||
pthread_mutex_init(&mutex, NULL); |
|
||||
pthread_cond_init(&condition, NULL); |
|
||||
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_cond_t condition; |
|
||||
}; |
|
||||
|
|
||||
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 = 0; |
|
||||
result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, NULL, NULL); |
|
||||
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("play-audio: underflow when prefetching data\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_PREFETCHSTATUS is general:
|
|
||||
SLuint32 const numWantedInterfaces = 2; |
|
||||
SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ANDROIDCONFIGURATION, SL_IID_PREFETCHSTATUS }; |
|
||||
SLboolean wantedInterfacesRequired[numWantedInterfaces]{ 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); |
|
||||
|
|
||||
result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &this->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); |
|
||||
|
|
||||
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; |
|
||||
char* streamType = NULL; |
|
||||
while ((c = getopt(argc, argv, "hs:")) != -1) { |
|
||||
switch (c) { |
|
||||
case 'h': |
|
||||
case '?': help = true; break; |
|
||||
case 's': streamType = optarg; break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (help || optind == argc) { |
|
||||
printf("usage: play-audio [-s streamtype] [files]\n"); |
|
||||
return 1; |
|
||||
} |
|
||||
|
|
||||
AudioPlayer player; |
|
||||
|
|
||||
if (streamType != NULL) { |
|
||||
SLint32 streamTypeEnum; |
|
||||
if (strcmp("alarm", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_ALARM; |
|
||||
} else if (strcmp("media", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_MEDIA; |
|
||||
} else if (strcmp("notification", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_NOTIFICATION; |
|
||||
} else if (strcmp("ring", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_RING; |
|
||||
} else if (strcmp("system", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_SYSTEM; |
|
||||
} else if (strcmp("voice", streamType) == 0) { |
|
||||
streamTypeEnum = SL_ANDROID_STREAM_VOICE; |
|
||||
} else { |
|
||||
fprintf(stderr, "play-audio: invalid streamtype '%s'\n", streamType); |
|
||||
return 1; |
|
||||
} |
|
||||
player.setStreamType(streamTypeEnum); |
|
||||
} |
|
||||
|
|
||||
for (int i = optind; i < argc; i++) { |
|
||||
if (access(argv[i], R_OK) != 0) { |
|
||||
fprintf(stderr, "play-audio: '%s' is not a readable file\n", argv[i]); |
|
||||
return 1; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
for (int i = optind; i < argc; i++) { |
|
||||
player.play(argv[i]); |
|
||||
} |
|
||||
|
|
||||
return 0; |
|
||||
} |
|
Loading…
Reference in new issue