前提(質問までに至るまでの過程)
更新箇所
・一応、回答を見た後に作ったソースコードを掲載。
+ループを止めるメソッド追加
・本文が10000文字以上になったらしい…ので一部いらない所修正。
・一番最初のwav → mp3へと変換した際にカットしている事を伝え忘れていた。
・無音分が増えたと分かってからは毎回カットしている。
Java8 Eclipseを使用しています。
外部ライブラリはmp3をJavaで扱う為のJavazoomを使用しています。
mp3やwavファイルの曲の長さを調べる為に波形ソフトのAudacityというソフトを使用しました。
初めてJavaでゲーム制作をしています。(ドラクエ1レベル程度の内容)
フリー音源を集めてBGMを鳴らそうと思ったのですが
mp3だと再生できないので、オーディオデータをwavに変換しました。
Clipでwavを鳴らしてみると、とても簡単に実装できました。
BGMもいつループしたのか分からない程スムーズにループしていました。
(mp3 → wavにした際、カットしてます)
しかし、そこで問題が発生しました。
容量が全部合わせて1GB以上(曲)+200MB(効果音)になりました。
mp3だとその4分の1くらいです。
ならば、mp3で再生できればと思い調べてみました。
①JAVAFXを使用しました。
・AudioClip → BGMのループに一瞬ラグが一瞬ありました。
・mediaplayer → BGMのループに一瞬ラグが一瞬ありました。
②外部ライブラリを使用する。
・JavazoomのBasicPlayer → BGMのループに一瞬ラグがありました。
全く解決策が見つからなく、ここではない質問サイトに質問すると
(プログラミングに特化した質問サイトがあるとは知らずに)
mp3→wavに変換してClipで再生すると
いいのではないかという回答がありました。
当初は、clipで問題ないのならwavに変換してから鳴らせば問題ないかなと思っていました。
しかし、実際にmp3→wav の変換プログラムを使用してclipで鳴らしてみても
BGMの一瞬のラグは直りませんでした。
そこで調べてみると、mp3→wav、wav→mp3に変換する際に
無音が前後に挿入されてしまうという事も知りました。
波形ソフトで見ると、mp3→wav(その逆も)に変換したものには前後に無音が挿入されていました。
(変換プログラムにも、無音が挿入されてしまっていた)
曲の長さも増えていました。全体 曲頭 曲後
11秒623→11秒677 +0.054秒 +0.051 +0.003
18秒099→18秒155 +0.056秒 +0.051 +0.005
16秒032→16秒091 +0.059秒 +0.051 +0.008
08秒546→08秒620 +0.074秒 +0.051 +0.023
30秒322→30秒380 +0.058秒 +0.051 +0.007
20秒051→20秒114 +0.063秒 +0.051 +0.012
3分51秒999→3分52秒072 全体 +0.073秒 曲頭 +0.051 曲後 +0.022
曲の前に0.000~0.051まで無音が挿入されました。何故かこれは固定です。何故追加されてるのかも不明です。
曲の後ろは、上の表みたいなのの通りです。曲毎にバラバラです。
ですが、曲の頭の無音+曲の後ろの無音を追加すると= wavの長さ - mp3の長さ = 無音追加分 になります。
引用で
『具体例として,サンプル周波数44.1kHzの1秒のMP3を作ることを考えると、必要なMP3フレーム数は44100÷1152=38.3となり、小数点は切り上げ、39フレームということになる。39フレームでMP3ファイルを作ると,デコード後の曲長は 39×1152サンプル(0.0261秒)= 1.0179秒になってしまい,正確に1秒のMP3ファイルは作れない。つまり、ファイル末尾に約0.02秒弱の無音部分が付いてしまう。この末尾に付加された余計なサンプルのことを,パッディングと呼ぶ」』
との事で mp3→wavですが、同じ事が起きているのではないかと考えています。
実現したいこと
JAVAでmp3をラグなくループする方法
調べても無かった為
ゲームでBGMを鳴らす際にmp3→wavで変換後、無音部分のカット。
・mp3→wavで増えた曲の長さから無音分を求める。
・曲の前後に無音が増える。曲の頭は0.051固定。曲の後ろは wavの全体の長さ - mp3の長さ - 0.051で求まる。
・それをカットする。
・無音カット版wavを再生する。
① mp3の全体の長さを調べたい。・・・問題点その1
② wavの全体の長さを調べる。
③ wavの長さ - mp3の長さ = 無音が追加された分を求めたい。
④ 無音追加分-0.051(曲前無音部分) で曲の後ろの無音部分が求められる。
⑤ wavの曲の頭から0.051秒カットしたい。・・・問題点その2
⑥ wavの曲の後ろから④で求めた数字分カットしたい。・・・問題点その3
⑦ wavの無音カットVerが出来上がる。
発生している問題・エラーメッセージ・試したこと
問題点その1
mp3をAudacityで測定すると曲は11.623秒と表示されます。
しかし、Mediainfoというソフトでmp3を見ると11秒650と表示されます。
Javaでmp3の曲の長さを調べるプログラムが記事に載っていたので試すと
(外部ライブラリJavazoom必要かも…)
File file = new File("filename.mp3");
AudioFileFormat baseFileFormat = new MpegAudioFileReader().getAudioFileFormat(file);
Map properties = baseFileFormat.properties();
Long duration = (Long) properties.get("duration");
duration = 11677000になりました。
なりましたが…それはwavに変換した際のAudacityで調べた長さと一緒でした。
変換前の値は11.623秒なので、mp3の長さが
duration = 11623000となってほしいです…
Javaでmp3の取り扱いが少なく、さらに曲の長さを求めるサンプルコードは
これくらいしかありませんでした…(Java sound api のメソッドだと-1になる)
問題点その2と3
曲の頭から 0.051秒カット
曲の後ろから wavの長さ - mp3の長さ - 0.051 秒分をカット
すれば良いのですが
JAVAでどうカットすればいいのか、オーディオデータを取り扱っている記事が
無く困っています。サンプルコード無いと何も出来ないへっぽこなので
非常に困っています…
mp3で11.623秒の曲を変換すると11.677秒になりました。差は0.054秒です。
0.051を頭からカットします。0.054-0.051=0.003秒を曲の後ろからカットします。
しかし、どうやってオーディオデータからその分を削除する方法が分かりません。
該当のソースコード
これはmp3→wav変換のプログラムです。mp3の曲の長さもついでに調べようとしてます。
wavの長さは709632と出ました。709632/44100 = 16.9142857142857...なので表3番目と一致してます。
色々と試しているので、ごちゃごちゃしています。
JAVA
1package test3; 2import java.io.File; 3import java.io.IOException; 4import java.util.Map; 5 6import javax.sound.sampled.AudioFileFormat; 7import javax.sound.sampled.AudioFileFormat.Type; 8import javax.sound.sampled.AudioFormat; 9import javax.sound.sampled.AudioInputStream; 10import javax.sound.sampled.AudioSystem; 11import javax.sound.sampled.UnsupportedAudioFileException; 12 13import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; 14 15public class Mudecod { 16 17 public static void main(String[] args) throws IOException, UnsupportedAudioFileException ,Exception{ 18 19 File mp3 = new File("src/ルナとアリスの不思議なダンジョン/resource/music/test/loop3-3.mp3"); 20 21 File mp3Data = mp3; 22 // open stream 23 AudioInputStream mp3Stream = AudioSystem.getAudioInputStream(mp3Data); 24 AudioFormat sourceFormat = mp3Stream.getFormat(); 25 // create audio format object for the desired stream/audio format 26 // this is *not* the same as the file format (wav) 27 28 AudioFileFormat baseFileFormat = new MpegAudioFileReader().getAudioFileFormat(mp3Data); 29 Map properties = baseFileFormat.properties(); 30 long duration = (long) properties.get("duration"); 31 32 System.out.println("duration = " + duration); 33 34 AudioFormat convertFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 35 sourceFormat.getSampleRate(), 16, 36 sourceFormat.getChannels(), 37 sourceFormat.getChannels() * 2, 38 sourceFormat.getSampleRate(), 39 false); 40 // create stream that delivers the desired format 41 AudioInputStream converted = AudioSystem.getAudioInputStream(convertFormat, mp3Stream); 42// AudioFormat convertedFormat = converted.getFormat(); 43 // write stream into a file with file format wav 44 AudioSystem.write(converted, Type.WAVE, new File("src/ルナとアリスの不思議なダンジョン/resource/music/test/loop3-3.mp3-conveted.wav")); 45 46 47 //mp3のサンプル周波数の値1152で割った値が出た。44100÷1152=38.28125 48 System.out.println(sourceFormat.getFrameRate()); 49 50 //サンプル周波数が出た。44100.0 51 //戻り値は圧縮されていないオーディオ・データのサンプル・レートとの事。 52 System.out.println(sourceFormat.getSampleRate()); 53 54 //チャンネル数2 55 System.out.println(sourceFormat.getChannels()); 56 57 //エンコードタイプMPEG1L3 58 System.out.println(sourceFormat.getEncoding()); 59 60 //サンプルのサイズを取得します。何故か-1 61 System.out.println(sourceFormat.getSampleSizeInBits()); 62 63 //このメソッドを先に見つけたかった。 64 //MPEG1L3 44100.0 Hz, unknown bits per sample, stereo, unknown frame size, 38.28125 frames/second, 65 System.out.println(sourceFormat.toString()); 66 67 //何故か一度wavで出力しないと-1と出てしまうので出力したwavファイルで。 68 File wav = new File("src/ルナとアリスの不思議なダンジョン/resource/music/test/loop3-3.mp3-conveted.wav"); 69 AudioInputStream stream = AudioSystem.getAudioInputStream(wav); 70 AudioFormat format = stream.getFormat(); 71 72 long length2 = stream.getFrameLength(); 73 float frame2 = format.getSampleRate(); 74 75 System.out.println(length2); //709632 76 System.out.println(frame2); //44100.0 77 float time = (float) (length2 / frame2); 78 System.out.println(time);//16.091429 79 80 /* 81 * 38.28125という数字が出てきたという事は、1152サンプル確定なのだから 82 * 1152/44100 0.02612244889795918... 83 * 84 * */ 85 86 87 } 88} 89
試行錯誤中
「EDIT40秒部分の抽出について
デコーダの出力はpcmサンプルです。 入力が16ビットステレオ44100Hzの場合、各フレームは16ビット×2チャンネル= 4バイト、1秒は44100×4バイトです。目的の部分が始まるまで必要なだけ出力バイトをスキップし、次に44100 * 4 * 40バイトを40秒間ダンプします。モノラルにミキシングしてから8ビットにカットすることもできます。 」
44100Hz、2ch、16ビットはそのままなので問題はないはず。
との事で、内容までのbyteを飛ばして、無音分を計算し飛ばす。
AudioStreamは、ヘッダー等は飛ばされるような記事みた気もするけれど。
0.051秒分飛ばして、そこからmp3の長さ-0.051秒間分ダンプ分ダンプする。
問題点その1 の解決策を探しています。
未だに取っ掛かりすらありません。
補足
プログラミングはJavaが初めてのJavaを初めて5ヵ月と31日です。
ゲーム制作も今回が初めてです。
外部ライブラリを使ったのも今回初めてです。(便利ですね)
BGMのループの一瞬のラグ程度、気にしないと思う人が大半だと思います。
でも可能な限り、不可能じゃない限り実装できるのならしたいと思い
2週間くらい調べています。(BGMを鳴らす所からだと結構前から)
しかし、ここから全く進展が無くなったので、今回質問させて頂きました。
そして、もし、こんな周りくどい事をしなくても
Javaでmp3のループ再生がラグなく出来るのでしたら
教えて頂ければ幸いです…
ogg について
Java sound api でoggを取り扱う為のライブラリー下記2種。
JOrbis:Javaで実装されたoggファイル再生ライブラリ
JOgg:JOrbisを使用したOggファイル再生プレーヤー
を追加しまして
TN8001様のサンプルコードを実行しました。
ラグなくループ再生出来ました。
https://soundengine.jp/wordpress/tips/glossary/454/ から引用
「非圧縮の音声ファイルに比べ、ファイルサイズを小さくできる。
圧縮前の音を完全に再現できない。(非可逆圧縮)
ライセンスの制限が緩やかなので、PCゲームによく利用されている。
ギャップレス再生に対応」
→「ギャップレス再生に対応」
さらに引用
「Oggは同ビットレートのMP3よりも聴感上良い音で再生でき、さらに、AACやWMAなど、他の非可逆圧縮の形式と比較しても引けを取らないようです。特に低ビットレートではその性能は高く評価されています。」
との事です。
oggもmp3と同じだと思っていました…
結論
JAVAでmp3をラグなくループする方法
沢山の記事を探しましたが見つかりませんでした。
wavに変換して、それを編集するくらいしか思いつきません。
挿入される無音部分の計算も出来ていないので
自分には難しかったみたいです。
JavaでBGMをラグなくループする方法は
oggを使うと良い事が分かりました。
一応 回答を見た後に作ってみました
(ゲームで使用させて頂いています)
色んな用途に使えるように、ファイルパスだけ渡せば
簡易プレイヤーとして活用できるようなクラスにしました。
+ループを止めるメソッドを追加しました。
ゲームの場合マルチスレッド何たら等
書いてある記事もありましたが
必要になったら学ぶ予定…
java
1 2/* 3 * MusicPlayerクラスです。 4 * このクラスは他プロジェクトでも汎用性があるように作られたクラスです。 5 * Java sound apiで再生できるデータ(wav、raw、midi)があるファイルの場所を 6 * コンストラクタで受け取ります。 7 * MusicPlayerクラスのsPlayメソッドで曲を再生します。 8 * MusicPlayerクラスのsStopメソッドで曲を停止します。 9 * 10 * 追伸: 11 * 外部ライブラリJavazoomをプロジェクトに入れるとmp3も再生されます。 12 * 外部ライブラリjorbisをプロジェクトに入れるとoggも再生されます。 13 * */ 14 15import java.io.File; 16import java.io.IOException; 17 18import javax.sound.sampled.AudioFormat; 19import javax.sound.sampled.AudioInputStream; 20import javax.sound.sampled.AudioSystem; 21import javax.sound.sampled.Clip; 22import javax.sound.sampled.LineUnavailableException; 23import javax.sound.sampled.UnsupportedAudioFileException; 24 25public class MusicPlayer { 26 private File musicData; 27 private AudioInputStream in; 28 private AudioInputStream dataIn; 29 private Clip clip; 30 31 public MusicPlayer(File musicData) { 32 this.musicData = musicData; 33 fileDataReading(); 34 } 35 36 public void fileDataReading() { 37 try { 38 in = AudioSystem.getAudioInputStream(musicData); 39 } catch (UnsupportedAudioFileException | IOException e) { 40 e.printStackTrace(); 41 } 42 AudioFormat baseFormat = in.getFormat(); 43 AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44 baseFormat.getSampleRate(), 16, 45 baseFormat.getChannels(), 46 baseFormat.getChannels() * 2, 47 baseFormat.getSampleRate(), false); 48 dataIn = AudioSystem.getAudioInputStream(targetFormat, in); 49 } 50 51 public void sPlay() { 52 try { 53 clip = AudioSystem.getClip(); 54 clip.open(dataIn); 55 clip.loop(Clip.LOOP_CONTINUOUSLY); 56 } catch (LineUnavailableException e) { 57 e.printStackTrace(); 58 } catch (IOException e) { 59 e.printStackTrace(); 60 } 61 } 62 63 public void sStop() { 64 try { 65 clip.close(); 66 dataIn.close(); 67 in.close(); 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } 71 } 72 73 public void loopStop() { 74 clip.loop(0); 75 } 76}
回答1件
あなたの回答
tips
プレビュー