Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2355edbc authored by Phil Burk's avatar Phil Burk
Browse files

OboeService: initial commit



This builds a standalone service that is easy to test.

Bug: 33269952
Test: test_oboe_api

Change-Id: I1890b1b974e728c2c0c15e24aa02121c2774bd56
Signed-off-by: default avatarPhil Burk <philburk@google.com>
parent cac8d4ea
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
LOCAL_PATH:= $(call my-dir)

# Oboe Service
include $(CLEAR_VARS)

LOCAL_MODULE := oboeservice
LOCAL_MODULE_TAGS := optional

LIBOBOE_DIR := ../../media/liboboe
LIBOBOE_SRC_DIR := $(LIBOBOE_DIR)/src

LOCAL_C_INCLUDES := \
    $(call include-path-for, audio-utils) \
    frameworks/native/include \
    system/core/base/include \
    $(TOP)/frameworks/native/media/liboboe/include/include \
    $(TOP)/frameworks/av/media/liboboe/include \
    frameworks/native/include \
    $(TOP)/external/tinyalsa/include \
    $(TOP)/frameworks/av/media/liboboe/src \
    $(TOP)/frameworks/av/media/liboboe/src/binding \
    $(TOP)/frameworks/av/media/liboboe/src/client \
    $(TOP)/frameworks/av/media/liboboe/src/core \
    $(TOP)/frameworks/av/media/liboboe/src/fifo \
    $(TOP)/frameworks/av/media/liboboe/src/utility

# TODO These could be in a liboboe_common library
LOCAL_SRC_FILES += \
    $(LIBOBOE_SRC_DIR)/utility/HandleTracker.cpp \
    $(LIBOBOE_SRC_DIR)/utility/OboeUtilities.cpp \
    $(LIBOBOE_SRC_DIR)/fifo/FifoBuffer.cpp \
    $(LIBOBOE_SRC_DIR)/fifo/FifoControllerBase.cpp \
    $(LIBOBOE_SRC_DIR)/binding/SharedMemoryParcelable.cpp \
    $(LIBOBOE_SRC_DIR)/binding/SharedRegionParcelable.cpp \
    $(LIBOBOE_SRC_DIR)/binding/RingBufferParcelable.cpp \
    $(LIBOBOE_SRC_DIR)/binding/AudioEndpointParcelable.cpp \
    $(LIBOBOE_SRC_DIR)/binding/OboeStreamRequest.cpp \
    $(LIBOBOE_SRC_DIR)/binding/OboeStreamConfiguration.cpp \
    $(LIBOBOE_SRC_DIR)/binding/IOboeAudioService.cpp \
    SharedRingBuffer.cpp \
    FakeAudioHal.cpp \
    OboeAudioService.cpp \
    OboeServiceStreamBase.cpp \
    OboeServiceStreamFakeHal.cpp \
    OboeServiceMain.cpp

LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_CFLAGS += -Wall -Werror

LOCAL_SHARED_LIBRARIES :=  libbinder libcutils libutils liblog libtinyalsa

include $(BUILD_EXECUTABLE)
+227 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Simple fake HAL that supports ALSA MMAP/NOIRQ mode.
 */

#include <iostream>
#include <math.h>
#include <limits>
#include <string.h>
#include <unistd.h>

#define __force
#define __bitwise
#define __user
#include <sound/asound.h>

#include "tinyalsa/asoundlib.h"

#include "FakeAudioHal.h"

//using namespace oboe;

using sample_t = int16_t;
using std::cout;
using std::endl;

#undef SNDRV_PCM_IOCTL_SYNC_PTR
#define SNDRV_PCM_IOCTL_SYNC_PTR 0xc0884123
#define PCM_ERROR_MAX 128

const int SAMPLE_RATE = 48000;       // Hz
const int CHANNEL_COUNT = 2;

struct pcm {
    int fd;
    unsigned int flags;
    int running:1;
    int prepared:1;
    int underruns;
    unsigned int buffer_size;
    unsigned int boundary;
    char error[PCM_ERROR_MAX];
    struct pcm_config config;
    struct snd_pcm_mmap_status *mmap_status;
    struct snd_pcm_mmap_control *mmap_control;
    struct snd_pcm_sync_ptr *sync_ptr;
    void *mmap_buffer;
    unsigned int noirq_frames_per_msec;
    int wait_for_avail_min;
};

static int pcm_sync_ptr(struct pcm *pcm, int flags) {
    if (pcm->sync_ptr) {
        pcm->sync_ptr->flags = flags;
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)
            return -1;
    }
    return 0;
}

int pcm_get_hw_ptr(struct pcm* pcm, unsigned int* hw_ptr) {
    if (!hw_ptr || !pcm) return -EINVAL;

    int result = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
    if (!result) {
        *hw_ptr = pcm->sync_ptr->s.status.hw_ptr;
    }

    return result;
}

typedef struct stream_tracker {
    struct pcm * pcm;
    int          framesPerBurst;
    sample_t   * hwBuffer;
    int32_t      capacityInFrames;
    int32_t      capacityInBytes;
} stream_tracker_t;

#define FRAMES_PER_BURST_QUALCOMM 192
#define FRAMES_PER_BURST_NVIDIA   128

int fake_hal_open(int card_id, int device_id, fake_hal_stream_ptr *streamPP) {
    int framesPerBurst = FRAMES_PER_BURST_QUALCOMM; // TODO update as needed
    int periodCount = 32;
    unsigned int offset1;
    unsigned int frames1;
    void *area = nullptr;
    int mmapAvail = 0;

    // Configuration for an ALSA stream.
    pcm_config cfg;
    memset(&cfg, 0, sizeof(cfg));
    cfg.channels = CHANNEL_COUNT;
    cfg.format = PCM_FORMAT_S16_LE;
    cfg.rate = SAMPLE_RATE;
    cfg.period_count = periodCount;
    cfg.period_size = framesPerBurst;
    cfg.start_threshold = 0; // for NOIRQ, should just start, was     framesPerBurst;
    cfg.stop_threshold = INT32_MAX;
    cfg.silence_size = 0;
    cfg.silence_threshold = 0;
    cfg.avail_min = framesPerBurst;

    stream_tracker_t *streamTracker = (stream_tracker_t *) malloc(sizeof(stream_tracker_t));
    if (streamTracker == nullptr) {
        return -1;
    }
    memset(streamTracker, 0, sizeof(stream_tracker_t));

    streamTracker->pcm = pcm_open(card_id, device_id, PCM_OUT | PCM_MMAP | PCM_NOIRQ, &cfg);
    if (streamTracker->pcm == nullptr) {
        cout << "Could not open device." << endl;
        free(streamTracker);
        return -1;
    }

    streamTracker->framesPerBurst = cfg.period_size; // Get from ALSA
    streamTracker->capacityInFrames = pcm_get_buffer_size(streamTracker->pcm);
    streamTracker->capacityInBytes = pcm_frames_to_bytes(streamTracker->pcm, streamTracker->capacityInFrames);
    std::cout << "fake_hal_open() streamTracker->framesPerBurst = " << streamTracker->framesPerBurst << std::endl;
    std::cout << "fake_hal_open() streamTracker->capacityInFrames = " << streamTracker->capacityInFrames << std::endl;

    if (pcm_is_ready(streamTracker->pcm) < 0) {
        cout << "Device is not ready." << endl;
        goto error;
    }

    if (pcm_prepare(streamTracker->pcm) < 0) {
        cout << "Device could not be prepared." << endl;
        cout << "For Marlin, please enter:" << endl;
        cout << "   adb shell" << endl;
        cout << "   tinymix \"QUAT_MI2S_RX Audio Mixer MultiMedia8\" 1" << endl;
        goto error;
    }
    mmapAvail = pcm_mmap_avail(streamTracker->pcm);
    if (mmapAvail <= 0) {
        cout << "fake_hal_open() mmap_avail is <=0" << endl;
        goto error;
    }
    cout << "fake_hal_open() mmap_avail = " << mmapAvail << endl;

    // Where is the memory mapped area?
    if (pcm_mmap_begin(streamTracker->pcm, &area, &offset1, &frames1) < 0)  {
        cout << "fake_hal_open() pcm_mmap_begin failed" << endl;
        goto error;
    }

    // Clear the buffer.
    memset((sample_t*) area, 0, streamTracker->capacityInBytes);
    streamTracker->hwBuffer = (sample_t*) area;
    streamTracker->hwBuffer[0] = 32000; // impulse

    // Prime the buffer so it can start.
    if (pcm_mmap_commit(streamTracker->pcm, 0, framesPerBurst) < 0) {
        cout << "fake_hal_open() pcm_mmap_commit failed" << endl;
        goto error;
    }

    *streamPP = streamTracker;
    return 1;

error:
    fake_hal_close(streamTracker);
    return -1;
}

int fake_hal_get_mmap_info(fake_hal_stream_ptr stream, mmap_buffer_info *info) {
    stream_tracker_t *streamTracker = (stream_tracker_t *) stream;
    info->fd = streamTracker->pcm->fd; // TODO use tinyalsa function
    info->hw_buffer = streamTracker->hwBuffer;
    info->burst_size_in_frames = streamTracker->framesPerBurst;
    info->buffer_capacity_in_frames = streamTracker->capacityInFrames;
    info->buffer_capacity_in_bytes = streamTracker->capacityInBytes;
    info->sample_rate = SAMPLE_RATE;
    info->channel_count = CHANNEL_COUNT;
    return 0;
}

int fake_hal_start(fake_hal_stream_ptr stream) {
    stream_tracker_t *streamTracker = (stream_tracker_t *) stream;
    if (pcm_start(streamTracker->pcm) < 0) {
        cout << "fake_hal_start failed" << endl;
        return -1;
    }
    return 0;
}

int fake_hal_pause(fake_hal_stream_ptr stream) {
    stream_tracker_t *streamTracker = (stream_tracker_t *) stream;
    if (pcm_stop(streamTracker->pcm) < 0) {
        cout << "fake_hal_stop failed" << endl;
        return -1;
    }
    return 0;
}

int fake_hal_get_frame_counter(fake_hal_stream_ptr stream, int *frame_counter) {
    stream_tracker_t *streamTracker = (stream_tracker_t *) stream;
    if (pcm_get_hw_ptr(streamTracker->pcm, (unsigned int *)frame_counter) < 0) {
        cout << "fake_hal_get_frame_counter failed" << endl;
        return -1;
    }
    return 0;
}

int fake_hal_close(fake_hal_stream_ptr stream) {
    stream_tracker_t *streamTracker = (stream_tracker_t *) stream;
    pcm_close(streamTracker->pcm);
    free(streamTracker);
    return 0;
}
+58 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Simple fake HAL that supports ALSA MMAP/NOIRQ mode.
 */

#ifndef FAKE_AUDIO_HAL_H
#define FAKE_AUDIO_HAL_H

//namespace oboe {

using sample_t = int16_t;
struct mmap_buffer_info {
    int       fd;
    int32_t   burst_size_in_frames;
    int32_t   buffer_capacity_in_frames;
    int32_t   buffer_capacity_in_bytes;
    int32_t   sample_rate;
    int32_t   channel_count;
    sample_t *hw_buffer;
};

typedef void *fake_hal_stream_ptr;

//extern "C"
//{

int fake_hal_open(int card_id, int device_id, fake_hal_stream_ptr *stream_pp);

int fake_hal_get_mmap_info(fake_hal_stream_ptr stream, mmap_buffer_info *info);

int fake_hal_start(fake_hal_stream_ptr stream);

int fake_hal_pause(fake_hal_stream_ptr stream);

int fake_hal_get_frame_counter(fake_hal_stream_ptr stream, int *frame_counter);

int fake_hal_close(fake_hal_stream_ptr stream);

//} /* "C" */

//} /* namespace oboe */

#endif // FAKE_AUDIO_HAL_H
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "OboeService"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#include <time.h>
#include <pthread.h>

#include <oboe/OboeDefinitions.h>

#include "HandleTracker.h"
#include "IOboeAudioService.h"
#include "OboeService.h"
#include "OboeAudioService.h"
#include "OboeServiceStreamFakeHal.h"

using namespace android;
using namespace oboe;

typedef enum
{
    OBOE_HANDLE_TYPE_STREAM,
    OBOE_HANDLE_TYPE_COUNT
} oboe_service_handle_type_t;
static_assert(OBOE_HANDLE_TYPE_COUNT <= HANDLE_TRACKER_MAX_TYPES, "Too many handle types.");

oboe_handle_t OboeAudioService::openStream(oboe::OboeStreamRequest &request,
                                                oboe::OboeStreamConfiguration &configuration) {
    OboeServiceStreamBase *serviceStream =  new OboeServiceStreamFakeHal();
    ALOGD("OboeAudioService::openStream(): created serviceStream = %p", serviceStream);
    oboe_result_t result = serviceStream->open(request, configuration);
    if (result < 0) {
        ALOGE("OboeAudioService::openStream(): open returned %d", result);
        return result;
    } else {
        OboeStream handle = mHandleTracker.put(OBOE_HANDLE_TYPE_STREAM, serviceStream);
        ALOGD("OboeAudioService::openStream(): handle = 0x%08X", handle);
        if (handle < 0) {
            delete serviceStream;
        }
        return handle;
    }
}

oboe_result_t OboeAudioService::closeStream(oboe_handle_t streamHandle) {
    OboeServiceStreamBase *serviceStream = (OboeServiceStreamBase *)
            mHandleTracker.remove(OBOE_HANDLE_TYPE_STREAM,
                                  streamHandle);
    ALOGI("OboeAudioService.closeStream(0x%08X)", streamHandle);
    if (serviceStream != nullptr) {
        ALOGD("OboeAudioService::closeStream(): deleting serviceStream = %p", serviceStream);
        delete serviceStream;
        return OBOE_OK;
    }
    return OBOE_ERROR_INVALID_HANDLE;
}

OboeServiceStreamBase *OboeAudioService::convertHandleToServiceStream(
        oboe_handle_t streamHandle) const {
    return (OboeServiceStreamBase *) mHandleTracker.get(OBOE_HANDLE_TYPE_STREAM,
                              (oboe_handle_t)streamHandle);
}

oboe_result_t OboeAudioService::getStreamDescription(
                oboe_handle_t streamHandle,
                oboe::AudioEndpointParcelable &parcelable) {
    ALOGI("OboeAudioService::getStreamDescriptor(), streamHandle = 0x%08x", streamHandle);
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::getStreamDescriptor(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        return OBOE_ERROR_INVALID_HANDLE;
    }
    return serviceStream->getDescription(parcelable);
}

oboe_result_t OboeAudioService::startStream(oboe_handle_t streamHandle) {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::startStream(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        return OBOE_ERROR_INVALID_HANDLE;
    }
    mLatestHandle = streamHandle;
    return serviceStream->start();
}

oboe_result_t OboeAudioService::pauseStream(oboe_handle_t streamHandle) {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::pauseStream(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        return OBOE_ERROR_INVALID_HANDLE;
    }
    return serviceStream->pause();
}

oboe_result_t OboeAudioService::flushStream(oboe_handle_t streamHandle) {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::flushStream(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        return OBOE_ERROR_INVALID_HANDLE;
    }
    return serviceStream->flush();
}

void OboeAudioService::tickle() {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(mLatestHandle);
    //ALOGI("OboeAudioService::tickle(), serviceStream = %p", serviceStream);
    if (serviceStream != nullptr) {
        serviceStream->tickle();
    }
}

oboe_result_t OboeAudioService::registerAudioThread(oboe_handle_t streamHandle,
                                                         pid_t clientThreadId,
                                                         oboe_nanoseconds_t periodNanoseconds) {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::registerAudioThread(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        ALOGE("OboeAudioService::registerAudioThread(), serviceStream == nullptr");
        return OBOE_ERROR_INVALID_HANDLE;
    }
    if (serviceStream->getRegisteredThread() != OboeServiceStreamBase::ILLEGAL_THREAD_ID) {
        ALOGE("OboeAudioService::registerAudioThread(), thread already registered");
        return OBOE_ERROR_INVALID_ORDER;
    }
    serviceStream->setRegisteredThread(clientThreadId);
    // Boost client thread to SCHED_FIFO
    struct sched_param sp;
    memset(&sp, 0, sizeof(sp));
    sp.sched_priority = 2; // TODO use 'requestPriority' function from frameworks/av/media/utils
    int err = sched_setscheduler(clientThreadId, SCHED_FIFO, &sp);
    if (err != 0){
        ALOGE("OboeAudioService::sched_setscheduler() failed, errno = %d, priority = %d",
              errno, sp.sched_priority);
        return OBOE_ERROR_INTERNAL;
    } else {
        return OBOE_OK;
    }
}

oboe_result_t OboeAudioService::unregisterAudioThread(oboe_handle_t streamHandle,
                                                           pid_t clientThreadId) {
    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
    ALOGI("OboeAudioService::unregisterAudioThread(), serviceStream = %p", serviceStream);
    if (serviceStream == nullptr) {
        ALOGE("OboeAudioService::unregisterAudioThread(), serviceStream == nullptr");
        return OBOE_ERROR_INVALID_HANDLE;
    }
    if (serviceStream->getRegisteredThread() != clientThreadId) {
        ALOGE("OboeAudioService::unregisterAudioThread(), wrong thread");
        return OBOE_ERROR_ILLEGAL_ARGUMENT;
    }
    serviceStream->setRegisteredThread(0);
    return OBOE_OK;
}
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef OBOE_OBOE_AUDIO_SERVICE_H
#define OBOE_OBOE_AUDIO_SERVICE_H

#include <time.h>
#include <pthread.h>

#include <binder/BinderService.h>

#include <oboe/OboeDefinitions.h>
#include <oboe/OboeAudio.h>
#include "HandleTracker.h"
#include "IOboeAudioService.h"
#include "OboeService.h"
#include "OboeServiceStreamBase.h"

using namespace android;
namespace oboe {

class OboeAudioService :
    public BinderService<OboeAudioService>,
    public BnOboeAudioService
{
    friend class BinderService<OboeAudioService>;   // for OboeAudioService()
public:
// TODO why does this fail?    static const char* getServiceName() ANDROID_API { return "media.audio_oboe"; }
    static const char* getServiceName() { return "media.audio_oboe"; }

    virtual oboe_handle_t openStream(OboeStreamRequest &request,
                                     OboeStreamConfiguration &configuration);

    virtual oboe_result_t closeStream(oboe_handle_t streamHandle);

    virtual oboe_result_t getStreamDescription(
                oboe_handle_t streamHandle,
                AudioEndpointParcelable &parcelable);

    virtual oboe_result_t startStream(oboe_handle_t streamHandle);

    virtual oboe_result_t pauseStream(oboe_handle_t streamHandle);

    virtual oboe_result_t flushStream(oboe_handle_t streamHandle);

    virtual oboe_result_t registerAudioThread(oboe_handle_t streamHandle,
                                              pid_t pid, oboe_nanoseconds_t periodNanoseconds) ;

    virtual oboe_result_t unregisterAudioThread(oboe_handle_t streamHandle, pid_t pid);

    virtual void tickle();

private:

    OboeServiceStreamBase *convertHandleToServiceStream(oboe_handle_t streamHandle) const;

    HandleTracker mHandleTracker;
    oboe_handle_t mLatestHandle = OBOE_ERROR_INVALID_HANDLE; // TODO until we have service threads
};

} /* namespace oboe */

#endif //OBOE_OBOE_AUDIO_SERVICE_H
Loading