質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

3377閲覧

Wavファイルの並列合成でノイズが乗る

teraterm

総合スコア12

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

1グッド

1クリップ

投稿2018/01/03 02:19

Java8にて、2つのWavファイルを並列に合成したいと考え色々調べたところ、データを足すと合成されるとあったので、単純に足してみたところ、一応ファイルは合成されたのですが、音が重なった部分に「サー」というノイズが乗ってしまい、きれいな合成がされません。

原因は何でしょうか。

Wavファイルは2つとも
frameSize:2byte
Fs:11025.0Hz
16bit
1ch
LittleEndian
SignedPCM
です。

宜しくお願い致します。

java

1import java.io.ByteArrayInputStream; 2import java.io.File; 3import java.io.InputStream; 4 5import javax.sound.sampled.AudioFileFormat; 6import javax.sound.sampled.AudioFormat; 7import javax.sound.sampled.AudioInputStream; 8import javax.sound.sampled.AudioSystem; 9 10public class WavMix { 11 12 public static void main(String[] args) throws Exception { 13 14 byte[] data1 = new WavMix().getByte("AAA.wav"); 15 byte[] data2 = new WavMix().getByte("BBB.wav"); 16 17 System.out.println("data1.length = " + data1.length); 18 System.out.println("data2.length = " + data2.length); 19 20 int activeLength = data1.length > data2.length ? data1.length : data2.length; 21 22 // 並列合成 23 byte[] data = new byte[activeLength]; 24 for (int i = 0; i < activeLength; i = i + 2) { 25 if (data1.length > i && data2.length > i) { 26 short n1 = (short) ((data1[i + 1] << 8) + data1[i]); 27 short n2 = (short) ((data2[i + 1] << 8) + data2[i]); 28 29 // 足す 30 short n = (short) ((n1 + n2) * 0.5); 31 32 String hex = String.format("%04x", n); 33 byte z1 = (byte) Integer.parseInt(hex.substring(0, 2), 16); 34 byte z2 = (byte) Integer.parseInt(hex.substring(2, 4), 16); 35 data[i] = z2; 36 data[i + 1] = z1; 37 } else if (data1.length > i) { 38 data[i] = data1[i]; 39 data[i + 1] = data1[i + 1]; 40 } else { 41 data[i] = data2[i]; 42 data[i + 1] = data2[i + 1]; 43 } 44 } 45 46 // 書き出し 47 AudioFormat af = new AudioFormat(22050, 16, 1, true, false); 48 InputStream in = new ByteArrayInputStream(data); 49 AudioInputStream newAis = new AudioInputStream(in, af, data.length); 50 File soundFile = new File("newFile.wav"); 51 AudioSystem.write(newAis, AudioFileFormat.Type.WAVE, soundFile); 52 53 } 54 55 // データ取得 56 byte[] getByte(String file) throws Exception { 57 AudioInputStream ais = AudioSystem.getAudioInputStream(new File(file)); 58 byte[] data = new byte[ais.available()]; 59 ais.read(data); 60 ais.close(); 61 format(ais); 62 return data; 63 64 } 65 66 // ファイルフォーマット確認 67 void format(AudioInputStream ais) { 68 AudioFormat format = ais.getFormat(); 69 StringBuilder sb = new StringBuilder(); 70 sb.append("[AudioFormat] frameSize:" + format.getFrameSize() + "byte"); 71 sb.append(" Fs:" + format.getSampleRate() + "Hz"); 72 sb.append(" " + format.getSampleSizeInBits() + "bit"); 73 sb.append(" " + format.getChannels() + "ch"); 74 if (format.isBigEndian()) { 75 sb.append(" BigEndian"); 76 } else { 77 sb.append(" LittleEndian"); 78 } 79 if (format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED) { 80 sb.append(" SignedPCM"); 81 } else if (format.getEncoding() == AudioFormat.Encoding.PCM_UNSIGNED) { 82 sb.append(" UnsignedPCM"); 83 } else { 84 sb.append(" NoPCM"); 85 } 86 System.out.println(sb.toString()); 87 } 88 89} 90
Nanai10a👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

javaのbyteはsignedです。演算(本件でいえば加算)の際にintに昇格されます。例えば16bit符号付のあるサンプル値が-2(LittleEndian)の場合低位バイトと高位バイトは次のようになります

16進数でのバイト列:0xFE(低位) 0xFF(高位)
それをjavaのbyteとして取り出すと...
低位 : -2
高位 : -1

つまりintに昇格される際に負号拡張されるのです

(byte)0xFF -> (int)0xFFFFFFFF

それゆえ以下の計算式では演算結果が意図したものとなりません。

short n1 = (short) ((data1[i + 1] << 8) + data1[i]);
=> short n1 = (short) (( ((byte)0xFF) << 8) + ((byte)0xFF));
=> short n1 = (short) (( ((int)0xFFFFFFFE) << 8) + ((int)0xFFFFFFFF));
=> -258 (期待は-2)

こうした場合、低位のバイトは加算の前に0xFFとの論理積をとり負号拡張されないようにする必要があります。(いうまでもなくn1, n2の両方について)

誤:short n1 = (short) ((data1[i + 1] << 8) + data1[i]);
正:short n1 = (short) ((data1[i + 1] << 8) + (data1[i] & 0xFF));


補記: 問題の原因とは関係なく結果は間違ってませんが効率上の指摘を1点。

合成後のサンプルデータ(short型)をbyteへ分解する演算ですが、ご質問のコードでは16進数4桁のStringへ変換してその前後の2文字を16進数整数としてパースする方法を採っておられます。しかしその方法よりも整数演算を用いた方が効率的です。Stringのインスタンスを生成しないことでヒープの消費もありませんし、処理自体のスピードもずっと速いです。

java

1short n = ...; 2data[i] = (byte)n; 3data[i] = (byte)(n >> 8);

投稿2018/01/03 02:43

編集2018/01/03 02:57
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teraterm

2018/01/03 03:06

的確なご回答ありがとうございます。 試してみたところ、きれいに合成できました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問