A sound pool implementation for iOS

This commit is contained in:
Chinmay Garde 2016-01-20 14:48:50 -08:00
parent 2ccac4c983
commit fd02ee30cb
5 changed files with 376 additions and 1 deletions

View File

@ -46,6 +46,8 @@ if (is_android) {
"ios/media_player_impl.mm",
"ios/media_service_impl.h",
"ios/media_service_impl.mm",
"ios/sound_pool_impl.h",
"ios/sound_pool_impl.mm",
]
deps = [

View File

@ -10,6 +10,7 @@
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "sky/services/media/media.mojom.h"
#include "sky/services/media/ios/media_player_impl.h"
#include "sky/services/media/ios/sound_pool_impl.h"
namespace sky {
namespace services {
@ -29,6 +30,7 @@ class MediaServiceImpl : public ::media::MediaService {
private:
mojo::StrongBinding<::media::MediaService> binding_;
MediaPlayerFactory media_player_;
SoundPoolFactory sound_pool_;
DISALLOW_COPY_AND_ASSIGN(MediaServiceImpl);
};

View File

@ -23,7 +23,7 @@ void MediaServiceImpl::CreatePlayer(
void MediaServiceImpl::CreateSoundPool(
mojo::InterfaceRequest<::media::SoundPool> pool,
int32_t max_streams) {
DCHECK(false);
sound_pool_.Create(nullptr, pool.Pass());
}
void MediaServiceFactory::Create(

View File

@ -0,0 +1,76 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_
#define SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_
#include "base/macros.h"
#include "mojo/public/cpp/application/interface_factory.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "sky/services/media/media.mojom.h"
#include "sky/services/media/ios/media_player_impl.h"
#if __OBJC__
@class SoundPoolClient;
#else // __OBJC__
class SoundPoolClient;
#endif // __OBJC__
namespace sky {
namespace services {
namespace media {
class SoundPoolImpl : public ::media::SoundPool {
public:
explicit SoundPoolImpl(mojo::InterfaceRequest<::media::SoundPool> request);
~SoundPoolImpl() override;
void Load(mojo::ScopedDataPipeConsumerHandle data_source,
const ::media::SoundPool::LoadCallback& callback) override;
void Play(int32_t sound_id,
int32_t stream_id,
mojo::Array<float> channel_volumes,
bool loop,
float rate,
const ::media::SoundPool::PlayCallback& callback) override;
void Stop(int32_t stream_id) override;
void Pause(int32_t stream_id) override;
void Resume(int32_t stream_id) override;
void SetRate(int32_t stream_id, float rate) override;
void SetVolume(int32_t stream_id,
mojo::Array<float> channel_volumes) override;
void PauseAll() override;
void ResumeAll() override;
private:
mojo::StrongBinding<::media::SoundPool> binding_;
SoundPoolClient* sound_pool_;
void onCopyToTemp(const ::media::SoundPool::LoadCallback& callback,
base::FilePath path,
bool success);
DISALLOW_COPY_AND_ASSIGN(SoundPoolImpl);
};
class SoundPoolFactory : public mojo::InterfaceFactory<::media::SoundPool> {
public:
void Create(mojo::ApplicationConnection* connection,
mojo::InterfaceRequest<::media::SoundPool> request) override;
};
} // namespace media
} // namespace services
} // namespace sky
#endif // SKY_SERVICES_MEDIA_IOS_SOUND_POOL_IMPL_H_

View File

@ -0,0 +1,295 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sky/services/media/ios/sound_pool_impl.h"
#include "base/bind.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/message_loop/message_loop.h"
#include "mojo/data_pipe_utils/data_pipe_utils.h"
#import <Foundation/Foundation.h>
#include <AVFoundation/AVFoundation.h>
@interface SoundPoolClient : NSObject
- (BOOL)loadPlayer:(NSURL*)url streamOut:(int32_t*)stream;
- (BOOL)play:(int32_t)stream;
- (BOOL)play:(int32_t)stream
volume:(float)volume
loop:(BOOL)loop
rate:(float)rate;
- (BOOL)resume:(int32_t)stream;
- (void)stop:(int32_t)stream;
- (void)pause:(int32_t)stream;
- (void)setRate:(float)rate stream:(int32_t)stream;
- (void)setVolume:(float)volume stream:(int32_t)stream;
- (void)pauseAll;
- (void)resumeAll;
@end
@implementation SoundPoolClient {
NSMutableDictionary* _players; // keyed by stream ID
int32_t _lastID;
}
- (instancetype)init {
self = [super init];
if (self) {
_players = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)loadPlayer:(NSURL*)url streamOut:(int32_t*)stream {
if (stream == NULL) {
return NO;
}
NSError* error = NULL;
AVAudioPlayer* player =
[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (error != NULL) {
[player release];
return NO;
}
if (![player prepareToPlay]) {
[player release];
return NO;
}
int32_t streamID = ++_lastID;
_players[@(streamID)] = player;
[player release];
*stream = streamID;
return YES;
}
- (AVAudioPlayer*)playerForID:(int32_t)stream {
return _players[@(stream)];
}
- (BOOL)play:(int32_t)stream {
return [[self playerForID:stream] playAtTime:0.0];
}
- (BOOL)play:(int32_t)stream
volume:(float)volume
loop:(BOOL)loop
rate:(float)rate {
AVAudioPlayer* player = [self playerForID:stream];
if (player == NULL) {
return NO;
}
if (volume > 1.0) {
volume = 1.0;
}
if (volume < 0.0) {
volume = 0.0;
}
player.volume = volume;
player.numberOfLoops = loop ? -1 : 0;
player.rate = rate;
return [player playAtTime:0.0];
}
- (BOOL)resume:(int32_t)stream {
return [[self playerForID:stream] play];
}
- (void)stop:(int32_t)stream {
return [[self playerForID:stream] stop];
}
- (void)pause:(int32_t)stream {
return [[self playerForID:stream] pause];
}
- (void)setRate:(float)rate stream:(int32_t)stream {
[self playerForID:stream].rate = rate;
}
- (void)setVolume:(float)volume stream:(int32_t)stream {
if (volume > 1.0) {
volume = 1.0;
}
if (volume < 0.0) {
volume = 0.0;
}
[self playerForID:stream].volume = volume;
}
- (void)pauseAll {
for (AVAudioPlayer* player in [_players allValues]) {
[player pause];
}
}
- (void)resumeAll {
for (AVAudioPlayer* player in [_players allValues]) {
[player play];
}
}
- (void)dealloc {
[_players release];
[super dealloc];
}
@end
namespace sky {
namespace services {
namespace media {
SoundPoolImpl::SoundPoolImpl(mojo::InterfaceRequest<::media::SoundPool> request)
: binding_(this, request.Pass()),
sound_pool_([[SoundPoolClient alloc] init]) {}
SoundPoolImpl::~SoundPoolImpl() {
[sound_pool_ release];
}
static base::FilePath TemporaryFilePath() {
char temp[256] = {0};
NSString* tempDirectory = NSTemporaryDirectory();
snprintf(temp, sizeof(temp), "%spool.XXXXXX", tempDirectory.UTF8String);
base::FilePath path(mktemp(temp));
return path;
}
void SoundPoolImpl::Load(mojo::ScopedDataPipeConsumerHandle data_source,
const ::media::SoundPool::LoadCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;
// Copy the contents of the data source to a temporary file
auto path = TemporaryFilePath();
auto taskRunner = base::MessageLoop::current()->task_runner().get();
auto copyCallback = base::Bind(&SoundPoolImpl::onCopyToTemp,
base::Unretained(this), callback, path);
mojo::common::CopyToFile(data_source.Pass(), path, taskRunner, copyCallback);
}
void SoundPoolImpl::onCopyToTemp(
const ::media::SoundPool::LoadCallback& callback,
base::FilePath path,
bool success) {
base::mac::ScopedNSAutoreleasePool pool;
if (!success) {
callback.Run(false, 0);
return;
}
// After the copy, initialize the audio player instance in the pool
NSString* filePath =
[NSString stringWithUTF8String:path.AsUTF8Unsafe().c_str()];
NSURL* fileURL = [NSURL URLWithString:filePath];
int32_t streamID = 0;
BOOL loadResult = [sound_pool_ loadPlayer:fileURL streamOut:&streamID];
// Fire user callback
callback.Run(loadResult, loadResult ? streamID : 0);
}
static float AverageVolume(mojo::Array<float>& channel_volumes) {
// There is no provision to set individual channel volumes. Instead, we just
// average out the volumes and pass that to the sound pool.
size_t volumesCount = channel_volumes.size();
if (volumesCount == 0) {
return 1.0;
}
float sum = 0;
for (const auto& volume : channel_volumes) {
sum += volume;
}
return sum / volumesCount;
}
void SoundPoolImpl::Play(int32_t sound_id,
int32_t stream_id,
mojo::Array<float> channel_volumes,
bool loop,
float rate,
const ::media::SoundPool::PlayCallback& callback) {
base::mac::ScopedNSAutoreleasePool pool;
BOOL playResult = [sound_pool_ play:stream_id
volume:AverageVolume(channel_volumes)
loop:loop
rate:rate];
callback.Run(playResult);
}
void SoundPoolImpl::Stop(int32_t stream_id) {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ stop:stream_id];
}
void SoundPoolImpl::Pause(int32_t stream_id) {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ pause:stream_id];
}
void SoundPoolImpl::Resume(int32_t stream_id) {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ resume:stream_id];
}
void SoundPoolImpl::SetRate(int32_t stream_id, float rate) {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ setRate:rate stream:stream_id];
}
void SoundPoolImpl::SetVolume(int32_t stream_id,
mojo::Array<float> channel_volumes) {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ setVolume:AverageVolume(channel_volumes) stream:stream_id];
}
void SoundPoolImpl::PauseAll() {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ pauseAll];
}
void SoundPoolImpl::ResumeAll() {
base::mac::ScopedNSAutoreleasePool pool;
[sound_pool_ resumeAll];
}
void SoundPoolFactory::Create(
mojo::ApplicationConnection* connection,
mojo::InterfaceRequest<::media::SoundPool> request) {
new SoundPoolImpl(request.Pass());
}
} // namespace media
} // namespace services
} // namespace sky