blob: 338e5b79e3dbf1c38c9573242c684f2f0e6f9a15 [file] [log] [blame]
//
// Copyright (C) 2015 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.
//
// Test app to play audio at the HAL layer.
#include <android-base/logging.h>
#include <audio_utils/sndfile.h>
#include <hardware/audio.h>
#include <hardware/hardware.h>
#include <media/AudioTrack.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaSource.h>
#include "SineSource.h"
// This test only supports audio playback with 16-bit PCM.
const audio_format_t kAudioPlaybackFormat = AUDIO_FORMAT_PCM_16_BIT;
// Generate a sinusoidal wave.
//
// Parameters:
// sample_rate: Rate to sample the sine wave at.
// num_channels: Number of channels required. For stereo it should be 2 and
// for mono should be 1.
// total_bytes_required: Size of the buffer.
//
// Returns: A pointer to the buffer containing the data.
uint8_t* GenerateData(int sample_rate, int num_channels,
size_t total_bytes_required) {
uint8_t* data = new uint8_t[total_bytes_required];
android::sp<android::SineSource> source = new android::SineSource(
sample_rate, num_channels);
source->start(nullptr); // Initialize without any params.
// Read sine data.
size_t num_bytes_copied = 0;
android::MediaBuffer* buffer = nullptr;
while (num_bytes_copied < total_bytes_required) {
android::MediaSource::ReadOptions options;
source->read(&buffer, &options);
CHECK(buffer != nullptr);
if (buffer->size() >= total_bytes_required - num_bytes_copied) {
// If the amount of bytes left to write is greater than or equal to the
// size of the buffer, write the amount of bytes left and then exit.
memcpy(static_cast<void*>(data + num_bytes_copied), buffer->data(),
total_bytes_required - num_bytes_copied);
break;
} else {
// If the amount of bytes left to write is less than the size of the
// buffer, write all bytes in the buffer and update num_bytes_copied.
memcpy(static_cast<void*>(data+num_bytes_copied), buffer->data(),
buffer->size());
num_bytes_copied += buffer->size();
}
}
CHECK(buffer != nullptr);
buffer->release();
return data;
}
// Play a sine wave.
//
// Parameters:
// out_stream: A pointer to the output audio stream.
// config: A pointer to struct that contains audio configuration data.
//
// Returns: An int which has a non-negative number on success.
int PlaySineWave(audio_stream_out_t* out_stream, audio_config_t* config) {
// Get buffer size and generate data.
size_t buffer_size = out_stream->common.get_buffer_size(&out_stream->common);
int num_channels = audio_channel_count_from_out_mask(config->channel_mask);
uint8_t* data = GenerateData(config->sample_rate, num_channels, buffer_size);
const size_t kNumBuffersToWrite = 1000;
int rc = 0;
// Write kNumBuffersToWrite buffers to the audio hal.
for (size_t i = 0; i < kNumBuffersToWrite; i++) {
size_t bytes_wanted =
out_stream->common.get_buffer_size(&out_stream->common);
rc = out_stream->write(
out_stream, data,
bytes_wanted <= buffer_size ? bytes_wanted : buffer_size);
if (rc < 0) {
LOG(ERROR) << "Writing data to hal failed. (" << strerror(rc) << ")";
break;
}
}
return rc;
}
// Play audio from a WAV file.
//
// Parameters:
// out_stream: A pointer to the output audio stream.
// in_file: A pointer to a SNDFILE object.
// config: A pointer to struct that contains audio configuration data.
//
// Returns: An int which has a non-negative number on success.
int PlayFile(audio_stream_out_t* out_stream, SNDFILE* in_file,
audio_config_t* config) {
size_t buffer_size = out_stream->common.get_buffer_size(&out_stream->common);
size_t kFrameSize =
audio_bytes_per_sample(kAudioPlaybackFormat) *
audio_channel_count_from_out_mask(config->channel_mask);
short* data = new short[buffer_size / kFrameSize];
int rc = 0;
sf_count_t frames_read = 1;
while (frames_read != 0) {
size_t bytes_wanted =
out_stream->common.get_buffer_size(&out_stream->common);
frames_read = sf_readf_short(in_file, data, bytes_wanted / kFrameSize);
rc = out_stream->write(out_stream, data, frames_read * kFrameSize);
if (rc < 0) {
LOG(ERROR) << "Writing data to hal failed. (" << strerror(rc) << ")";
break;
}
}
return rc;
}
// Prints usage information if input arguments are missing.
void Usage() {
fprintf(stderr, "Usage: ./audio_hal_playback_test device sample_rate/file\n"
"If the test passes, you should hear either a beep for a few seconds played "
"at the specified sample rate or the specified file.\n"
"device: hex value representing the audio device (see "
"system/media/audio/include/system/audio.h)\n"
"Either the sample rate or a file must be passed as an argument.\n"
"sample_rate: Sample rate to play a sine wave.\n"
"file: 16-bit PCM wav file to play.\n");
}
int main(int argc, char* argv[]) {
if (argc < 3) {
Usage();
return -1;
}
// Process command line arguments.
const int kAudioDeviceBase = 16;
uint32_t desired_output_device = strtol(
argv[1], nullptr /* look at full string*/, kAudioDeviceBase);
const int kSampleRateBase = 10;
uint32_t desired_sample_rate = strtol(
argv[2], nullptr /* look at full string*/, kSampleRateBase);
char* filename = nullptr;
if (desired_sample_rate == 0) {
filename = argv[2];
}
LOG(INFO) << "Starting audio hal tests.";
int rc = 0;
const hw_module_t* module = nullptr;
// Load audio HAL.
rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, "primary", &module);
if (rc) {
LOG(WARNING) << "Could not get primary hw module, trying usb. (" << strerror(rc) << ")";
rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, "usb", &module);
if (rc) {
LOG(ERROR) << "Could not get hw module. (" << strerror(rc) << ")";
return -1;
}
}
// Open audio device.
CHECK(module != nullptr);
audio_hw_device_t* audio_device = nullptr;
rc = audio_hw_device_open(module, &audio_device);
if (rc) {
LOG(ERROR) << "Could not open hw device. (" << strerror(rc) << ")";
return -1;
}
// Set to a high number so it doesn't interfere with existing stream handles
// from AudioFlinger.
audio_io_handle_t handle = 0x999;
audio_devices_t output_device =
static_cast<audio_devices_t>(desired_output_device);
audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE;
audio_config_t config;
SF_INFO file_info;
SNDFILE* in_file = nullptr;
if (filename) {
in_file = sf_open(filename, SFM_READ, &file_info);
CHECK(in_file != nullptr);
config.channel_mask = audio_channel_out_mask_from_count(file_info.channels);
if (!(file_info.format & SF_FORMAT_PCM_16)) {
LOG(ERROR) << "File must be of format 16-bit PCM.";
return -1;
}
config.format = kAudioPlaybackFormat;
config.sample_rate = file_info.samplerate;
} else {
config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
config.format = kAudioPlaybackFormat;
config.sample_rate = desired_sample_rate;
}
LOG(INFO) << "Now playing to " << output_device << " at sample rate "
<< config.sample_rate;
const char* stream_name = "output_stream";
// Open audio output stream.
audio_stream_out_t* out_stream = nullptr;
CHECK(audio_device != nullptr);
rc = audio_device->open_output_stream(audio_device, handle, output_device,
flags, &config, &out_stream,
stream_name);
if (rc) {
LOG(ERROR) << "Could not open output stream. (" << strerror(rc) << ")";
return -1;
}
// Set volume.
const float kLeftVolume = 0.75;
const float kRightVolume = 0.75;
CHECK(out_stream != nullptr);
rc = out_stream->set_volume(out_stream, kLeftVolume, kRightVolume);
if (rc) {
LOG(ERROR) << "Could not set volume correctly. (" << strerror(rc) << ")";
}
if (filename) {
PlayFile(out_stream, in_file, &config);
} else {
PlaySineWave(out_stream, &config);
}
// Close output stream and device.
audio_device->close_output_stream(audio_device, out_stream);
audio_hw_device_close(audio_device);
LOG(INFO) << "Done with hal tests";
return 0;
}