ラベル サウンド の投稿を表示しています。 すべての投稿を表示
ラベル サウンド の投稿を表示しています。 すべての投稿を表示

2019年9月26日木曜日

iOS13でAVAudioPlayerが落ちるようになった

以下のようなコードでBGMやSEを鳴らすアプリをリリースしてたのだが、iOS13にアップデートしたら音を鳴らす所で落ちるようになってしまった。
もっと詳細には try player = AVAudioPlayer(contentsOf: url) で
Thread 1: EXC_BAD_ACCESS (code=1, address=0x48) - broken with 
というエラーコードで落ちた。リリースしていたアプリが落ちるのだから、iOS13のせいだ。

import UIKit
import AVFoundation

class ViewController: UIViewController {
var player = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
if let audioPath = Bundle.main.path(forResource: "BGM", ofType: "mp3") {
let url = URL(fileURLWithPath: audioPath)
try player = AVAudioPlayer(contentsOf: url)
}
} catch {
print("ERROR")
}
self.player.play()
}
}

調べたところ、頭でインスタンス変数として宣言している
var player = AVAudioPlayer()
を
var player:AVAudioPlayer!
とoptionalにしたところ、落ちなくなった。

変数作る際に初期化コードを書いても初期化されなくなる仕様変更があったんだろうか? そうなるとAVAudioPlayer以外でも同じ問題出てくる?

参考サイト


2019年4月30日火曜日

Music Libraryの音楽を鳴らす

MediaPlayer、AVPlayer、AVAudioPlayerと3種類方法があるようだ。
ミュージック.appのようなMPMediaPickerContorollerを使うことで曲を簡単に選択できる。

MediaPlayerを使う

簡単さから言えば一番簡単。
currentPlaybackRateプロパティがあるが、再生速度の変更がなぜかできない。原因不明。


import UIKit
import MediaPlayer //Frameworkをインストールしておくこと

class ViewController: UIViewController, MPMediaPickerControllerDelegate {
//再生を継続するためにインスタンス変数としてPlayerを用意
var player:MPMusicPlayerController!
override func viewDidLoad() {
super.viewDidLoad()
player = MPMusicPlayerController.applicationMusicPlayer
}

@IBAction func pickMusic(_ sender: Any) {
let picker = MPMediaPickerController()
picker.delegate = self
//曲の複数選択の有無
picker.allowsPickingMultipleItems = false
present(picker, animated: true, completion: nil)
}
//音楽が選択された時呼ばれるdelegate
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
player.setQueue(with: mediaItemCollection)
player.play() //曲の再生
//pickerを消す
dismiss(animated: true, completion: nil)
}
}

AVPlayerを使う

AVFoundationのimportが必要。
動画再生にも対応したclassのようだ。
ミュージックライブラリの曲のassetURLを取得する点がちょい面倒だが、簡単。
assetURLからPlayerを作るときはtry文が不要。
rateに負の数を入れれば逆再生もできる。
再生位置の指定はseekメソッドで、引数はCMTimeなので面倒のようだ。

AVAudioPlayerを使う

AVFoundationのimportが必要。
AVPlayerのAudio(音声)特化class?
ミュージックライブラリの曲のassetURLを取得する点がちょい面倒だが、簡単。
assetURLからPlayerを作るときはtry文が必要。
rateに負の数を入れても逆再生はできない。
再生位置の指定はcurrentTimeプロパティで簡単。単位は秒。公式の解説だと再生中は現在位置からのオフセット位置、停止中はplay()メソッド開始位置からのオフセット位置とあるが、どちらも変わらず、曲の先頭からのオフセット秒数みたい?

import UIKit
import MediaPlayer
import AVFoundation


class ViewController: UIViewController, MPMediaPickerControllerDelegate {
var player:AVAudioPlayer!

//音楽が選択された時呼ばれるdelegate
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
//選択された曲を取得
let items = mediaItemCollection.items
if items.isEmpty {
print("曲が選択されていない")
return
}
//先頭の一曲のassetURLを取得
let item = items[0]
if let url = item.assetURL {
do {
player = try AVAudioPlayer(contentsOf: url)
} catch {
print("曲が取得できなかった。DRMの関係?")
player = nil
return
}
//曲の再生
player.play()
} else {
print("assetURL取得できなかった")
}
//pickerを消す
dismiss(animated: true, completion: nil)
}
}

曲などの情報を得る

先のmediaPickerのdelegateの、mediaItemCollectionの要素に曲名、アーチスト名などの情報にプロパティとしてアクセスできるので、取り出すのは簡単。

//音楽が選択された時呼ばれるdelegate
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
let items = mediaItemCollection.items
if items.isEmpty {
print("曲が選択されていない")
return
}
let musicTitle = items[0].title //曲タイトル
let musicArtist = items[0].artist //アーチスト名
let albumTitle = items[0].albumTitle //アルバムタイトル
let albunArtist = items[0].albumArtist //アルバムアーチスト名

let artWork = items[0].artwork //アートワーク
imageView.image = artWork?.image(at: imageView.bounds.size)

//以下略
}

注意点

どのやり方をやっても、info.plistの Privacy - Media Library Usage Description を設定しないと
present(picker, animated: true, completion: nil)
でpickerが表示されない。
最近の傾向としてそうなのはわかるんだけど、エラーメッセージとか一切出てくれなかった。
似たようなので Privacy - Music Usage Description というのがあるので、間違えないこと。

2018年12月3日月曜日

変なエラー/Unknown selected data source for Port スピーカー (type: Speaker)

[avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port スピーカー (type: Speaker)

「スピーカーに対して、不明のデータソースが選ばれた」ってことか。

iOS12、Swift4.2、Xcode10になってから、上記のメッセージがずらずらと出るようになった。何も操作してないのに0.何秒かおきに次々と出る、もしくは数回出て止まったり。
ただWarningみたいなもので、実行上問題は出ていない。

音を鳴らすアプリではあるのだが、まだ音に関しての操作もコードもないViewControllerを実行中に出るので、コードの変更があったというよりはXcodeの設定を変更しなけりゃいけないのかもしれない。

ネットで調べると、Swift4.2でAVAudioSessionの関係で変更があり、try文を記述してどうのこうのと書いてあるが、音の操作関係のコードがないところでいっぱい出てるので、直しようがない。そもそもAVAudioSessionのコード書いてないし。

データソースが不明ということで音源ファイルが原因っぽいが、使ってるのはそれぞれmp3、m4r、m4aのフォーマットの音源なのだが、Xcodeの設定をどこかでいじらねばいけないのか?

原因はAdMob

広告にGoogleのAdMobを表示させてるんだけど、それが悪さしていたようで、AdMob表示させないようにしたらメッセージが出なくなった。

Google Mobile Ads SDK Developers掲示板でもその件が話されている。いつの投稿か日付がわかんないんだけど、最新の日付が2018/9/18頃らしいので、まあその少し前くらいだろう。
AdMob内でAVAudioSessionを使用してて、それの改修が遅れてるんだろう。
ちなみに現時点で最新のAdMobSDK 7.36.0(2018/9/10リリースらしい)にしてみたけどダメだった。
うざいメッセージだけど、実用上問題ないし、俺が悪いわけじゃないからいいや。さっさと直してほしいけどね。

Googleって高い給料もらってるすごいエンジニアばかりだと思うんだけど、こういうのを直すのがすごい遅い。直されないことも多いし。変な入社試験で人を選んでるせいでエンジニアが足りないの?

2018年1月31日水曜日

メモ:音の同時再生

情報源:【iOS】音楽を止めずに効果音を同時に再生するには

まだちゃんと試してないけど、情報見つけたのでメモ。
AppDelegateに以下のコードを書いておくと同時再生できるんだとか。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
    } catch {
        print("エラー")
    }
    
    return true

}

AVAudioPlayerで効果音を連続再生

やりたいこと

ボタンを押すたびにピコーンというSEを鳴らすのだが、高速で連続してボタンを押した場合に音が鳴らないことがある。

解決法

音を重ねて鳴らす場合はAVAudioPlayerをしまうプロパティを配列に入れ、再生が終わるまで保持するなどすればいい。(終わったのを消すのがSwiftではちょっと面倒になったけど)

重ねて鳴らさなくていいならば、前の音が再生中なら音を止め、再生ポイント(currentTime)を先頭に戻してやればいい。

具体的には以下のように。

if correctSound.isPlaying {
      correctSound.stop()
}
correctSound.currentTime = 0
correctSound.play()

2018年1月30日火曜日

AVAudioPlayerで初回再生遅延

問題

ゲームで使う短いサウンドをAVAudioPlayerで鳴らすのだが、初回だけ一瞬再生が遅れてしまう。2回目以降の再生では遅延は起きない。

soundPlayer?.prepareToPlay()
をあらかじめ処理しておいてもダメ。

volume = 0
であらかじめ1度再生させ、直後にvolumeを元に戻してみたら、なぜか音が鳴ってしまう。

解決法

現在不明。

2015年3月20日金曜日

効果音を鳴らす

正解したら「ピンポ〜ン」、不正解なら「ブッ!」といった簡単なSEを鳴らしたい場合。
21世紀にもなったのに、やり方がちょっととっても面倒なのでまとめる。
なお、Swift 2.0もわかった点だけ後述。

鳴らすためのサウンドファイルはどっかから用意すること。GarageBandあたりで作ってもよし。

ファイルをXcodeにドラッグしてプロジェクトに登録するまでは画像なんかと一緒。
しかし次が重要。
ドラッグして追加した場合、Build PhasesのCopy Bundle Resourcesで、「+」ボタンを押してサウンドファイルを指定してやらないと、コード中でpathが掴めなくてエラーになってしまう。
ただし、ファイルメニューの「Add Files to "プロジェクト名"」で追加した場合は自動的にCopy Bundle Resourcesに追加されるので、不要のようだ。こっちの方が楽だな。

サウンドファイルを選んでAddを押す。

これでちゃんとサウンドファイルが認識されるようになる。

必要なFrameworkはAVFoundation.frameworkなので、インポートしとくこと。


ヘッダーファイルのinterface部でAVAudioPlayerDelegateの設定が必要。
@interface GameViewController : UIViewController <AVAudioPlayerDelegate>

実装ファイルの方でも工夫が必要。
サウンド用のインスタンス変数を用意。(後述)
NSMutableArray *soundArray; //SEのインスタンスを一時的にしまっておく
AVAudioPlayer *soundPlayer; //SE再生用

AVAudioPlayerってので再生するのだが、ローカル変数では、ARCで動かすせいで、メソッドを抜けるとすぐに変数が解放されてしまうため、音を鳴らすところまでいってくれないのだ。
ということでサウンドのインスタンスはインスタンス変数にしまって保持するのだが、一つのインスタンス変数だけでは、今度は前の音が終了するまで次の音が鳴らなくなってしまう。ギギギ…(-_-;)
そこで、NSMutableArrayに同時に鳴らす数だけのインスタンス変数をしまって保持するようにする。
そして音が鳴り終わった時に呼ばれるありがたいdelegateメソッドによって、不要なインスタンス変数をNSMutableArrayから消すのだ。

//サウンドの再生
- (void)playSoundWithResource:(NSString *)resource ofType:(NSString *)type{
    NSString *path = [[NSBundle mainBundle]pathForResource:resource ofType:type];
    
    soundPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
    soundPlayer.delegate = self;
    soundPlayer.volume = 0.4;
    soundPlayer.numberOfLoops = 0; //連続再生回数。0で1回、1で2回…、-1で無限ループ
    [soundPlayer prepareToPlay]; //あらかじめメモリに展開するなど、すぐ再生できるよう準備
    [soundPlayer play];
    [soundArray insertObject:soundPlayer atIndex:0]; //ARCでメソッド終了後に消されないようにインスタンス変数の配列に格納
}

//鳴り終わったら呼ばれるdelegateメソッド
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    //鳴り終わったSEのインスタンスを消す
    [soundArray removeObject:player];
}

上記のplaySoundWithResource:〜メソッドは自作なので、実際に音を鳴らす際は、どっかから以下のように呼び出す。
[self playSoundWithResource:@"correct" ofType:@"m4a"];

いちいちめんどくせえだろ? 8ビットパソコンでBEEPとだけ書けば良かったものが、この手間の数。世の中間違っとるよね。Swiftではどうなのか知らんけど。


BGMを鳴らす場合

ゲームなんかでBGMを鳴らしっぱなしにする際は、
soundPlayer.numberOfLoops = -1;
にしてやりゃいい。

BGMをGarageBandで作ると無音部分が追加される

共有でiTunesに書き出して、そのファイルを使うことになるわけだけど、共有メニューからiTunesやディスクに書き出すと、曲の最後に1秒程度の無音部分が勝手に追加されてしまう。
おかげで、BGM2巡目に入る前に音が途切れることとなり甚だよろしくない。
音の切り貼りができるソフト(PRO版のQuickTime7 Playerとか)を使って残響音のところを消してしまえば問題はないが、めんどくさいし、QTPlayerだと波形が見られないからちょうどいいところで削除できなかったり、AIFFかWaveなどでしか保存できないみたい。

40秒という制限はあるが、GarageBandの共有から着信音として書き出すと、連続再生に向いた、無音部分のないものが書き出せる。

なお、Xcodeにとっかえひっかえ曲のファイルを入れて試していたら、新しく追加した無音部分なしファイルが認識されず、差し替えたはずの無音部分付きファイルが再生されることがあって、やっぱりダメなのかと迷ったことがあった。
プロダクトのクリーン、Xcode再起動で直ったが、こういうことはXcodeではよくあるね。

Swift 2.0の場合

・AVFoundation.frameworkのインポートは必要
import AVFoundation

・ヘッダー部分でのAVAudioPlayerDelegateの記述は必要
class ViewController: UIViewController, AVAudioPlayerDelegate {

・インスタンス変数を定義しておくのも必要
    var soundPlayer:AVAudioPlayer? //SE再生用

・Swift 2.0の仕様変更でエラー処理の仕方が変わったため、AVAudioPlayer使用時に
do 〜 catch内にtry文を書く必要がある。
よく知らんのだけど、try後に書いたメソッドでエラーが発生したら、catch後の処理を実行するのだね。
    func playSoundWithResource(resource:String, type:String) {
        let path = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(resource, ofType: type)!)
        do {
            soundPlayer = try AVAudioPlayer(contentsOfURL: path)
            soundPlayer!.delegate = self
            soundPlayer!.volume = 0.4
            soundPlayer!.numberOfLoops = 0 //0で1回再生
            soundPlayer!.prepareToPlay()
            soundPlayer!.play()
        } catch {
            print("sound再生でエラー発生")
        } 
    }

tryのところは以下でもいいみたい。
try soundPlayer = AVAudioPlayer(contentsOfURL: path)

まあ後はテキトーに調べて書こう。