java使用AudioWaveformCreator绘制2(多个)波形
Posted
技术标签:
【中文标题】java使用AudioWaveformCreator绘制2(多个)波形【英文标题】:java drawing 2 (multiple) waveform using AudioWaveformCreator 【发布时间】:2019-01-24 14:35:15 【问题描述】:这个问题是关于 this 线程上用于回答的代码。我正在使用 Nicholas DiPiazza 发布的代码以及后来 Andrew Thompson 发布的变体。 我在此代码中添加了第二个 AudioWaveformCreator,结果对于两个 AWC 都是相同的。我不知道为什么。我要做的是在单个 JOptionpane 中显示 2 个不同的波形(来自不同的文件)。
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.Audiosystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class AudioWaveformCreator2
AudioInputStream audioInputStream;
Vector<Line2D.Double> lines = new Vector<Line2D.Double>();
String errStr;
Capture capture = new Capture();
double duration, seconds;
//File file;
String fileName = "out.png";
SamplingGraph samplingGraph;
String waveformFilename;
Color imageBackgroundColor = new Color(20,20,20);
Object result = null;
public AudioWaveformCreator2(File url, String waveformFilename) throws Exception
if (url != null)
try
errStr = null;
audioInputStream = AudioSystem.getAudioInputStream(url);
long milliseconds = (long)((audioInputStream.getFrameLength() * 1000) / audioInputStream.getFormat().getFrameRate());
duration = milliseconds / 1000.0;
samplingGraph = new SamplingGraph();
samplingGraph.createWaveForm(null);
catch (Exception ex)
reportStatus(ex.toString());
throw ex;
else
reportStatus("Audio file required.");
/**
* Render a WaveForm.
*/
class SamplingGraph implements Runnable
private Thread thread;
private Font font10 = new Font("serif", Font.PLAIN, 10);
private Font font12 = new Font("serif", Font.PLAIN, 12);
Color jfcBlue = new Color(000, 000, 255);
Color pink = new Color(255, 175, 175);
public SamplingGraph()
public void createWaveForm(byte[] audioBytes)
lines.removeAllElements(); // clear the old vector
AudioFormat format = audioInputStream.getFormat();
if (audioBytes == null)
try
audioBytes = new byte[
(int) (audioInputStream.getFrameLength()
* format.getFrameSize())];
audioInputStream.read(audioBytes);
catch (Exception ex)
reportStatus(ex.getMessage());
return;
int w = 500;
int h = 200;
int[] audioData = null;
if (format.getSampleSizeInBits() == 16)
int nlengthInSamples = audioBytes.length / 2;
audioData = new int[nlengthInSamples];
if (format.isBigEndian())
for (int i = 0; i < nlengthInSamples; i++)
/* First byte is MSB (high order) */
int MSB = (int) audioBytes[2*i];
/* Second byte is LSB (low order) */
int LSB = (int) audioBytes[2*i+1];
audioData[i] = MSB << 8 | (255 & LSB);
else
for (int i = 0; i < nlengthInSamples; i++)
/* First byte is LSB (low order) */
int LSB = (int) audioBytes[2*i];
/* Second byte is MSB (high order) */
int MSB = (int) audioBytes[2*i+1];
audioData[i] = MSB << 8 | (255 & LSB);
else if (format.getSampleSizeInBits() == 8)
int nlengthInSamples = audioBytes.length;
audioData = new int[nlengthInSamples];
if (format.getEncoding().toString().startsWith("PCM_SIGN"))
for (int i = 0; i < audioBytes.length; i++)
audioData[i] = audioBytes[i];
else
for (int i = 0; i < audioBytes.length; i++)
audioData[i] = audioBytes[i] - 128;
int frames_per_pixel = audioBytes.length / format.getFrameSize()/w;
byte my_byte = 0;
double y_last = 0;
int numChannels = format.getChannels();
for (double x = 0; x < w && audioData != null; x++)
int idx = (int) (frames_per_pixel * numChannels * x);
if (format.getSampleSizeInBits() == 8)
my_byte = (byte) audioData[idx];
else
my_byte = (byte) (128 * audioData[idx] / 32768 );
double y_new = (double) (h * (128 - my_byte) / 256);
lines.add(new Line2D.Double(x, y_last, x, y_new));
y_last = y_new;
saveToFile();
public void saveToFile()
int w = 500;
int h = 200;
int INFOPAD = 0;
BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bufferedImage.createGraphics();
createSampleOnGraphicsContext(w, h, INFOPAD, g2);
g2.dispose();
// Write generated image to a file
try
// Save as PNG
File file = new File(fileName);
System.out.println(file.getAbsolutePath());
ImageIO.write(bufferedImage, "png", file);
result = new ImageIcon(fileName);
catch (IOException e)
private void createSampleOnGraphicsContext(int w, int h, int INFOPAD, Graphics2D g2)
g2.setBackground(imageBackgroundColor);
g2.clearRect(0, 0, w, h);
g2.setColor(Color.white);
g2.fillRect(0, h-INFOPAD, w, INFOPAD);
if (errStr != null)
g2.setColor(jfcBlue);
g2.setFont(new Font("serif", Font.BOLD, 18));
g2.drawString("ERROR", 5, 20);
AttributedString as = new AttributedString(errStr);
as.addAttribute(TextAttribute.FONT, font12, 0, errStr.length());
AttributedCharacterIterator aci = as.getIterator();
FontRenderContext frc = g2.getFontRenderContext();
LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
float x = 5, y = 25;
lbm.setPosition(0);
while (lbm.getPosition() < errStr.length())
TextLayout tl = lbm.nextLayout(w-x-5);
if (!tl.isLeftToRight())
x = w - tl.getAdvance();
tl.draw(g2, x, y += tl.getAscent());
y += tl.getDescent() + tl.getLeading();
else if (capture.thread != null)
g2.setColor(Color.black);
g2.setFont(font12);
//g2.drawString("Length: " + String.valueOf(seconds), 3, h-4);
else
g2.setColor(Color.black);
g2.setFont(font12);
//g2.drawString("File: " + fileName + " Length: " + String.valueOf(duration) + " Position: " + String.valueOf(seconds), 3, h-4);
if (audioInputStream != null)
// .. render sampling graph ..
g2.setColor(jfcBlue);
for (int i = 1; i < lines.size(); i++)
g2.draw((Line2D) lines.get(i));
// .. draw current position ..
if (seconds != 0)
double loc = seconds/duration*w;
g2.setColor(pink);
g2.setStroke(new BasicStroke(3));
g2.draw(new Line2D.Double(loc, 0, loc, h-INFOPAD-2));
public void start()
thread = new Thread(this);
thread.setName("SamplingGraph");
thread.start();
seconds = 0;
public void stop()
if (thread != null)
thread.interrupt();
thread = null;
public void run()
seconds = 0;
while (thread != null)
if ( (capture.line != null) && (capture.line.isActive()) )
long milliseconds = (long)(capture.line.getMicrosecondPosition() / 1000);
seconds = milliseconds / 1000.0;
try thread.sleep(100); catch (Exception e) break;
while ((capture.line != null && !capture.line.isActive()))
try thread.sleep(10); catch (Exception e) break;
seconds = 0;
// End class SamplingGraph
/**
* Reads data from the input channel and writes to the output stream
*/
class Capture implements Runnable
TargetDataLine line;
Thread thread;
public void start()
errStr = null;
thread = new Thread(this);
thread.setName("Capture");
thread.start();
public void stop()
thread = null;
private void shutDown(String message)
if ((errStr = message) != null && thread != null)
thread = null;
samplingGraph.stop();
System.err.println(errStr);
public void run()
duration = 0;
audioInputStream = null;
// define the required attributes for our line,
// and make sure a compatible line is supported.
AudioFormat format = audioInputStream.getFormat();
DataLine.Info info = new DataLine.Info(TargetDataLine.class,
format);
if (!AudioSystem.isLineSupported(info))
shutDown("Line matching " + info + " not supported.");
return;
// get and open the target data line for capture.
try
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format, line.getBufferSize());
catch (LineUnavailableException ex)
shutDown("Unable to open the line: " + ex);
return;
catch (SecurityException ex)
shutDown(ex.toString());
//JavaSound.showInfoDialog();
return;
catch (Exception ex)
shutDown(ex.toString());
return;
// play back the captured audio data
ByteArrayOutputStream out = new ByteArrayOutputStream();
int frameSizeInBytes = format.getFrameSize();
int bufferLengthInFrames = line.getBufferSize() / 8;
int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
byte[] data = new byte[bufferLengthInBytes];
int numBytesRead;
line.start();
while (thread != null)
if((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1)
break;
out.write(data, 0, numBytesRead);
// we reached the end of the stream. stop and close the line.
line.stop();
line.close();
line = null;
// stop and close the output stream
try
out.flush();
out.close();
catch (IOException ex)
ex.printStackTrace();
// load bytes into the audio input stream for playback
byte audioBytes[] = out.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
audioInputStream = new AudioInputStream(bais, format, audioBytes.length / frameSizeInBytes);
long milliseconds = (long)((audioInputStream.getFrameLength() * 1000) / format.getFrameRate());
duration = milliseconds / 1000.0;
try
audioInputStream.reset();
catch (Exception ex)
ex.printStackTrace();
return;
samplingGraph.createWaveForm(audioBytes);
// End class Capture
public static void main(String [] args) throws Exception
AudioWaveformCreator2 awc = new AudioWaveformCreator2(new File("E:/PRODI ILKOM/Semester VIII/TA/wave/cars062.wav"), "cars062.png");
AudioWaveformCreator2 awc2 = new AudioWaveformCreator2(new File("E:/PRODI ILKOM/Semester VIII/TA/wave/plain wav.wav"), "plain wav.png");
Object[] fields =
"Plain", awc.result
,"Stego", awc2.result
;
JOptionPane.showConfirmDialog(null, fields, "Wave Form", JOptionPane.PLAIN_MESSAGE);
private void reportStatus(String msg)
if ((errStr = msg) != null)
System.out.println(errStr);
private static void printUsage()
System.out.println("AudioWaveformCreator usage: java AudioWaveformCreator.class [path to audio file for generating the image] [path to save waveform image to]");
这是我得到的两个波形:
【问题讨论】:
【参考方案1】:创建AudioWaveformCreator2
-instance 时,随后会执行SamplingGraph#saveToFile
-方法。该方法将先前生成的波形存储在文件fileName
中,其中fileName
是一个AudioWaveformCreator2
字段,初始化为固定 名称out.png
。因此,当创建多个AudioWaveformCreator2
-instance 时,两个实例都将它们的数据存储在相同 文件out.png
中,并且第二个文件会覆盖第一个文件。在AudioWaveformCreator2
-instance 存储文件后,使用ImageIcon(String filename)
-constructor 创建一个新的ImageIcon
。 ImageIcon
(例如http://hg.openjdk.java.net/jdk10/jdk10/jdk/file/777356696811/src/java.desktop/share/classes/javax/swing/ImageIcon.java)的源代码显示ImageIcon(String filename)
-构造函数稍后会调用Toolkit.getDefaultToolkit().getImage(filename)
-方法。
该方法的描述表明,有一种缓存机制可以为具有相同文件名的请求返回相同的图像(参见例如https://docs.oracle.com/javase/10/docs/api/java/awt/Toolkit.html#getImage(java.lang.String)):
返回从指定文件获取像素数据的图像,其 格式可以是 GIF、JPEG 或 PNG。底层工具包尝试 将具有相同文件名的多个请求解析为相同的 返回的图像。由于促进这一点所需的机制 图像对象的共享可能会继续保留不存在的图像 无限期使用更长的时间,开发人员 鼓励通过使用实现自己的图像缓存 可用的 createImage 变体。如果图像数据包含在 指定文件发生变化,由此返回的Image对象 方法可能仍然包含从 事先调用后的文件。以前加载的图像数据可以手动 通过在返回的 Image 上调用 flush 方法来丢弃。
缓存结合与固定名称out.png
负责观察到的行为:虽然第二个AudioWaveformCreator2
-instance
覆盖out.png
-文件,缓存机制提供第一张图片,因此第一张图片显示两次:
一个可能的解决方案是在AudioWaveformCreator2
-constructor 中传递和初始化文件名:
public AudioWaveformCreator2(File url, String waveformFilename, String fileName) throws Exception
if (url != null)
try
this.fileName = fileName;
...
和
AudioWaveformCreator2 awc = new AudioWaveformCreator2(new File("E:/PRODI ILKOM/Semester VIII/TA/wave/cars062.wav"), "cars062.png", "out.png");
AudioWaveformCreator2 awc2 = new AudioWaveformCreator2(new File("E:/PRODI ILKOM/Semester VIII/TA/wave/plain wav.wav"), "plain wav.png", "out2.png");
...
第一个AudioWaveformCreator2
-instance 将图像存储在文件out.png
中,第二个AudioWaveformCreator2
-instance 将图像存储在文件out2.png
中。然后,缓存机制可以区分两个图像:
还有其他解决方案可以保留在 same 文件中存储out.png
(即不需要修改AudioWaveformCreator2
-constructor),例如Toolkit.getDefaultToolkit().createImage(filename)
-method 的用法描述如下(参见例如https://docs.oracle.com/javase/10/docs/api/java/awt/Toolkit.html#createImage(java.lang.String)):
返回从指定 URL 获取像素数据的图像。这 返回的图像是一个新对象,不会与任何其他对象共享 此方法或其 getImage 变体的调用者。
因此,不涉及缓存机制,修复只是替换
result = new ImageIcon(fileName);
与
result = new ImageIcon(Toolkit.getDefaultToolkit().createImage(fileName));
同样避免缓存机制的第三种解决方案是替换
result = new ImageIcon(fileName);
与
result = new ImageIcon(bufferedImage);
因为ImageIcon(Image image)
-constructor不使用Toolkit.getDefaultToolkit().getImage(filename)
-方法,而是直接
bufferedImage
中包含的数据。
【讨论】:
以上是关于java使用AudioWaveformCreator绘制2(多个)波形的主要内容,如果未能解决你的问题,请参考以下文章