首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > JAVA > J2SE开发 >

请教怎么用java捕获音频播放软件发送到声卡的数据流

2011-12-12 
请教如何用java捕获音频播放软件发送到声卡的数据流?我想捕获播放软件发送到声卡音频数据流,请问如何实现?

请教如何用java捕获音频播放软件发送到声卡的数据流?
我想捕获播放软件发送到声卡音频数据流,请问如何实现?

[解决办法]
似乎是不行吧....
[解决办法]
 Java是一门适合于分布式计算环境、尤其是Internet程序设计的语言。这不仅仅在于java具有很好的安全性和可移植性,还在于java为Internet编程提供了丰富的网络类库的支持。利用这些网络类库,可以轻松编写多种类型的网络通信程序。然而由于某些限制,Java在传输多媒体信息方面的应用不是很广,大部分的应用都集中在网络上传输语音等音频信号的方面。传输音频信号应用方案一般有两种,一是应用于数据广播的多对一传输,例如音频数据服务器向数个客户端发送音频数据信号,其最广泛的应用则是某些网上的IP电话,大家经常可以看到不少这种提供在线IP电话服务的网站的客户端都是使用的嵌在网页上的Java Applet程序,用来实现拨号、通话等等基本的网络电话功能; 第二种方案则是我们今天要涉及的部分,一对一的音频信号数据的传输。这种方案的应用范围更广。大家都去过语音聊天室,大部分的语音聊天室的语音聊天功能的实现就是使用的Java技术,大家对这样网页的源代码分析一下就可以发现这一点。

  我曾开发一个项目,涉及使用java来实现在网络上传输语音数据。开发中遇到不少问题,而且在互联网上发现关于java语音传输的资料比较少,寻找了许多天,最终从一个开放源代码的一个简单的Answer Machine 演示程序中获得了解决问题的方法。今天我就把我在点对点传输音频信号方面的一些经验拿出来,与大家共同探讨这方面的问题。

  二、存在的问题

  在网络上传输音频的方面存在的问题主要可以归纳为以下几点:

  1 双方之间的网络连接

  要进行频数据的传输,首先就是要建立数据连结。常用的通讯协议中,TCP较可靠,所以用在不允许数据丢失的应用上。而UDP则较多应用于处理速度要求较快、数据传输可靠性要求不是很高的应用上,如数据广播。通信协议的选择取决于我们所要做的应用的类型。怎样建立网络连接,稳定的接收和发送音频信号的数据流是关键。

  2 音频信号的采集以及回放

  在进行音频信号的采集中我们必须考虑到采样率的问题,声音信号的采样率有8Khz、16Khz、32Khz、44Khz等,每种数据采样虑产生的数据量都不一样,越高的采样率产生的数据量越大,所以我们要选择合适的采样率以适应网络的带宽。

  3 音频数字信号的编码与解码。

  如果把直接采集到的音频信号数据流在网络上进行传输,它所占有的带宽也是十分大的,以8Khz的采样率采集14位的音频数据那么就有以下这样的一个式子:

14 bit * 8000/second=112,000 bits/second or112kbps

  从中我们可以看出以这样的方式传输音频数据,每秒需要向网络中发送112kb的数据。所以。从节省带宽的角度考虑,我们很有必要对这样的数据进行压缩。对多媒体信号的压缩我们有许多可以选择的格式,如mp2、mp3、GSM等等。同样,我们这里也存在一个对压缩格式进行选择的问题,考虑到音频数据传输的及时性,对传输的音频数据质量的要求,以及各种压缩格式的压缩比率以及进行压缩和解压缩所要耗费的系统资源等方面问题,选择合适的压缩格式就显得尤为重要。


三、解决的方法

  下面就针对前面提出的问题讨论一下解决的办法。

  1 双方之间的网络连接

  Java在这方面有其独特的优势,Java提供了丰富的网络类库的支持,可以轻松编写多种类型的网络通信程序。在我下面的例子中我就使用了TCP/IP协议,通过Java的Socket类进行编程。

  2 音频信号的采集和回放以及音频数字信号的编码与解码

  在解决这两个问题的时候,在网上很幸运地通过一些文章的介绍,找到了Answer Machine 演示程序的源代码(由of jsresources.org的Florian Bomers 和Matthias Pfisterer编写,网址http://www.jsresources.org/apps/am.html)。在这个程序代码中,有几个解决我们问题所需要的类,而且作者将这些类封装的很好,我们基本不需要做什么改动,只需要屏蔽其中的调试信息的输出就行了,更可贵的是它还封装了几种常见的音频格式。其中的GSM格式(Global System for Mobile Telecommunications)就是我们下面例子中采用的压缩格式,GSM格式可以将128kbps 的音频数据流 (16bit通过8k Hz的音频采样) 压缩为13kbps 的音频数据流,非常适合语音信号的传送,所以可谓是一石二鸟。

  我分析过这几个类的源代码,不得不佩服它的作者,每个类的源代码都很精炼,大家可以自己分析一下。好了下面就给大家讲讲这几个类,并且将它们用到的Java Sound API中的类和函数等一并做个简单介绍,让大家对Java Sound API中常用的类也有个大致的了解。由于Java Sound API中的类比较多。限于篇幅无法对所有用到的类做详尽的解释,以下内容只是简单提及了各个类的用途和使用规范,有关Java Sound API中类的具体介绍请大家访问这里http://java.sun.com/j2se/1.4.2/docs/api/, 查找javax.sound.sampled的相关内容。

  以下的提到几个文件是从Answer Machine 演示程序的源代码中提取出来的,由于是开放源代码的程序,大家在使用的时候请注意相关的公共协议。

  ① AMAudioFormat类(封装在AMAudioFormat.java文件中)

  AMAudioFormat类封装了CD、FM、TELEPHONE、GSM这四种质量的音频格式的参数,使用起来也非常简单,这样我们在使用Java Sound API时就不用自己去写那些复杂的代码了,但为了明白Java Sound API的原理,我们需要对它的代码做一下分析。它使用了Java Sound API中的AudioFormat这个类,这个类非常重要,在Java中对任何音频数据的使用都要实现通过它指定所需要使用的音频格式,AudioFormat类有一个嵌套的类AudioFormat.Encoding,实际上大部分对AudioFormat类的使用都是使用的这个嵌套的类。

  AMAudioFormat类的重要方法:

  名称:getLineAudioFormat
  调用格式:getLineAudioFormat(整型音频格式代号)
  返回值: 根据传递音频格式代号生成的AudioFormat对象。

  说道这里大家可能要问了,那么通过Java Sound API可以直接使用GSM格式吗?答案是比较复杂,但同样有解决的办法,作者在这里使用了另外的开源程序的类库-tritonus的GSM编码解码库。大家需要在这里www.tritonus.org/plugins.html下载tritonous_share.jar和tritonus_gsm.jar两个文件,并在AMAudioFormat类中引用,这样就完成了GSM格式的设置。需要告诉大家的是在对AMAudioFormat.java这个类进行编译后,我们的程序运行的时候就可以不需要tritonous_share.jar和tritonus_gsm.jar这两个文件的支持了。

  ② AudioCapture类(封装在AudioCapture.java文件中)

  AudioCapture类封装了从音频硬件捕获音频数据并自动编码为GSM音频压缩数据的过程,并且通过它的getAudioInputStream()方法提供给我们一个音频数据输入流,我们就可以直接将这个流发送到网络中。

  AudioCapture 类的重要方法:
  名称:getAudioInputStream
  调用格式:getAudioInputStream()


  返回值:AudioInputStream对象

  AudioCapture 类使用了Java Sound API中的AudioInputStream、AudioFormat、AudioSystem这几个类和TargetDataLine、LineListener接口。除了AudioFormat类我再简单介绍一下其他的类:

  AudioInputStream 类是带有特殊音频格式和长度的InputStream类,它有两个构造方法,分别是AudioInputStream(InputStream stream, AudioFormat format,long length)和AudioInputStream(TargetData -Line line)。

  TargetDataLine 接口是DataLine接口的一种,通过它就可以直接从音频硬件获取数据了,它有几个常用的方法,分别是:open(AudioFormat format)、void open(AudioFormat format, int bufferSize)、int read(byte[] b, int off, int len)。

  AudioSystem 类是Java标准音频系统的入口点,在AudioSystem 类中使用他的getLine() 方法创建TargetDataLine对象。

  LineListener接口用来对线路状态改变的时间进行监听,他的重要的方法是update(LineEvent event)方法。

  ③ AudioPlayStream类(封装在AudioPlayStream.java文件中)

  AudioPlayStream类与AudioCapture类刚好相反,它封装了GSM压缩音频数据的解码和音频信号的回放过程,提供给我们一个音频信号输出流。AudioCapture类用到的Java Sound API中的类它也基本都用到了,只是它使用了SourceDataLine接口而不是TargetDataLine接口

  ④ Debug类(封装在Debug.java文件中)

  Debug类主要用来在调试时输出讯息,代码很少,后来我把其中输出信息的语句都屏蔽了,对程序运行没有影响。

  为了方便使用以上的几个类,我们需要对它们进行编译和打包,编译时需要设置相关的编译环境,以下是我们需要用到的命令行

set CLASSPATH=%CLASSPATH%;.;tritonus_gsm.jar;tritonus_share.jar
javac am\*.java am\audio\*.java
jar cmf packaging\manifest.mf am.jar am\*.class
am\audio\*.class

  说明一下,我将以上提到的Java源码文件放在了am目录下,编译之后可以得到一个8k的am.jar文件,我们下一步所需要做的就是在我们的程序中引用这个包。

[解决办法]
在本章中,我们将逐步完成录制音频文件的过程。我们将使用类似于 IAudioPlayer 接口,称为 IAudioRecorder 的接口。
IAudioRecorder 接口非常象播放音频中演示的 IAudioPlayer,但它用于录制而不是回放。
与 IAudioPlayer 一样,IAudioRecorder 接口仅包含很通用的方法(例如,startRecording、stopRecording 和 save),
因此简化了使用并封装了特定于 Java Sound 的功能。

虽然关于播放音频的一章与关于录制的本章之间有许多相似之处,但在实现方面却有许多很重要的不同之处。
首先,没有简单的对象能象 Clip 简单地播放音频那样来录制声音。
结果,我们不得不直接同 I/O 流、线路、字节数组和音频数据转换等打交道。
在 AudioPlayer 示例中,我们学习了如何从给定的声音文件中检索音频格式。
然而在 AudioRecorder(IAudioRecorder 的基本实现)中,我们控制用于录制实际音频的设置。
由于一旦录制了音频就难以更改音频格式,所以这需要更多地关注音频设置。
每当需要弄清音频术语或概念时,请您参考数字音频速成课程。


*/


/*
接口 第 2 页(共12 页)




下面介绍 AudioRecorder 接口。AudioRecorder 接口与 AudioPlayer 之间有一点不同,
不同之处在于这个类需要考虑音频格式。对于 AudioPlayer,根据从读入的音频数据中检索的格式,
从 AudioSystem 类检索线路。
然而,在音频录制时,AudioRecorder 将控制录制音频所用的格式。

下面是 AudioRecorder 接口代码:
*/




public interface IAudioRecorder
{

/* start recording */
public void startRecording();

/* stop recording */
public void stopRecording();

/* reset all audio buffers, clear recording */
public void reset();

/* set AudioFormat used to record the audio */
public void setAudioFormat();

/* retrieve audio format used to record the audio */
public AudioFormat getAudioFormat();

/* set the default audio format, non-compressed
CD-quality settings */
public void setDefaultAudioFormat();

/* save the cached recording to a file */
public boolean
saveToFile(File file, AudioFileFormat.Type fileType);
}
/*

标准音频设置 第 3 页(共12 页)




对于缺省 AudioFormat,我们将使用 CD 音频设置和 WAV 文件格式以使示例简单。

下面是缺省音频格式的规范:

编码:带正负号的 PCM


采样率:44.1 kHz(CD 音质)


采样大小:16 位或两个 8 位字节(CD 音质)


声道数:两个或立体声(CD 音质)


帧大小:32 位或 4 字节(16 位采样大小 * 2 声道 = 每字节 32 位/8 位每字节 = 4 字节)




帧速率:44.1 kHz(采样率)


大尾数法:否
接下来,我们将为 IAudioRecorder 接口中的缺省音频格式声明 public static final 变量。下面是格式:

*/
public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat
(
//Encoding
AudioFormat.Encoding.PCM_SIGNED,

//Sample rate
44100f,

//Sample size in bytes
2,

//Number of channels
2,

//Frame size
4,

//Frame rate
44100f,

//Big endian
false
);
/*
除了我们必须实例化一个 AudioFormat 对象,而不是从 AudioInputStream 检索它之外,
检索输入 Line 的过程与检索 Clip 的过程相同。要检索一个输入 Line,请完成下列步骤:

实例化 AudioFormat 对象。


实例化 DataLine.Info 对象,该对象引用适当的 AudioFormat 并指定请求的 Line 类型。


从 AudioSystem 请求 Line。
与 AudioPlayer 示例相同,我们将请求一个 TargetDataLine 实例来接收音频输入而不是直接实例化 TargetDataLine。
在有了 TargetDataLine 实例之后,需要打开它以完成初始化。
与回放示例相同,AudioSystem 类返回一个缺少内容的容器。
另外,AudioRecorder 被设计成除非被覆盖,否则使用 DEFAULT_AUDIO_FORMAT。
下面是代码:

*/
//Create a DataLine.Info object with a TargetDataLine and the default format

DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
//Request the line from the AudioSystem

line = (TargetDataLine) AudioSystem.getLine(info);
//Open the line to complete the instantiation

line.open(format, line.getBufferSize());
/*
TargetDataLine 中的读方法 第 5 页(共12 页)

[解决办法]

一旦提供了音频,我们就需要使用 TargetDataLine 接口中的 read 方法读取并储存音频数据。
让我们查看 read 方法以了解如何捕获音频输入:


public int read(byte[] b, int offset, int length)


byte[] b:音频数据输入被写入这个 byte[]。
状态 int 是由 read 方法返回的,因此必须传入 byte[] 以使它只具有单个返回类型。

int offset:指定数组内的偏移量以开始写音频。
当在缓冲区中已有数据并且您想将新数据添加到已有数据的结尾时,可以使用偏移量。

对于我们简单的音频录制器用途,偏移量将始终为零。
这将新捕获的音频数据放在缓冲区的开始处。

int length:指定从音频输入读取的字节数。在从输入读取 length 个字节以前,该方法会一直阻塞。
因此,read 方法应该在其自己的线程中执行;否则,将冻结该接口。
假设 AudioRecorder 被正确线程化(但我不会详细讨论线程化,因为它超出本教程的范围)。
检查示例源代码(可以在参考资料中获取)以了解更多信息。

*/
/*

准备检索的字节数组 第 6 页(共12 页)




首先,我们需要计算传入 read 方法中的 byte 数组的大小。这是通过下列计算完成的:


//Frame size
int frameSizeInBytes = format.getFrameSize();

//The number of frames = the size of the buffer
int bufferLengthInFrames = line.getBufferSize() / 8;

//Number of frames * frame size = total bytes
int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;

//Make a new byte[] the size of the AudioBuffer
byte[] data = new byte[bufferLengthInBytes];


接下来,我们实例化一个输出流以将 byte 数组写入。
用这种方法,如果我们进行后续读取,则可以写入到输出流而不需要为数据管理编写更多代码。


ByteArrayOutputStream out = new ByteArrayOutputStream();


*/
/*

读取音频数据并对其进行高速缓存 第 7 页(共12 页)




现在,我们已经准备好读入音频。可以通过调用 TargetDataLine 中的 read 方法读取音频数据。
read 方法在 while 循环中,因此我们可以连续地录制而不用创建一个非常大的 byte[]。
从 TargetDataLine 中读取的数据将被写到 ByteArrayOutputStream 进行存储。
如果 read 方法返回 -1(表示错误),则我们还需要执行检查以中断循环。
下面是代码:


int numBytesRead; //Cache the number of bytes we have read
while (recording)


{
//Read in some data from the line
//Read will return -1 if it encounters errors
if((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1)
{
break;
}

//Once we read in the data, we'll write it to an
//audio output stream
out.write(data, 0, numBytesRead);
}
*/
/*

将 ByteArrayOutputStream 转换成 AudioInputStream 第 8 页(共12 页)




接下来,我们将把 ByteArrayOutputStream 转换成 AudioInputStream。这样做有两个原因:

将音频保存到一个文件
将音频缓冲区保存到文件之前回放它
下面是用于转换的代码:


//Convert the output stream back to a byte[]
byte[] audioBytes = out.toByteArray();

//Make a ByteArrayInputStream from the byte[]
ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);

//Request an AudioInputStream from the AudioSystem
//passing in the byte stream, audio format, and length
audioInputStream =
new AudioInputStream(bais, format, audioBytes.length/frameSizeInBytes);




*/
/*

将音频缓冲区保存到文件 第 9 页(共12 页)




既然我们已经有了 AudioInputStream,则我们可以调用 AudioSystem 中的 write 方法以将缓冲区保存到文件。

在保存该文件之前,我们需要指定 AudioFileFormat。AudioFileFormat 包含下列信息:

AudioFormat
AudioFileFormat.Type
AudioFileFormat.Type 是 AudioFileFormat 的一个内部类,它定义下列文件类型的全局常量:

AIFC
AIFF
AU
SND
WAV
我们需要提供输出文件和文件类型常量。
我们将假设调用方法将为我们指定 AudioFileFormat.Type 类.
下面是 save 方法的代码:


public boolean saveToFile(File file, AudioFileFormat.Type fileType)
throws Exception
{
//Reset to the beginning of the captured data
audioInputStream.reset();

//Notify if an error has occurred
return (AudioSystem.write(audioInputStream, fileType, file) == -1);
}



*/
/*

支持的文件类型 第 10 页(共12 页)




AudioFileFormat.Type 常量对应的文件类型不是保证受支持的。
您可以通过使用 isFileTypeSupported(AudioFileFormat.Type type) 方法询问 AudioSystem 类来确定受支持的文件类型。


*/
/*
回放音频缓冲区 第 11 页(共12 页)

[解决办法]
1.获得输出的途径
SourceDataLine line=AudioSystem.getLine()

2.constuct a sound format
AudioFormat format = ...
这个format许要和你采集是的format一致
3.open line
line.open(format)
4.line.writeByte(byte[] b)

具体的可以看sun的sound api 详情

http://java.sun.com/products/java-media/sound/index.html

这样采集的是输入吗?我没试过,以前想写这方面东西是找了很多资料
[解决办法]
给你一段java播放音频的代码,以前在网上找到的,我自己改过之后可以正常播放音频文件,具体的数据流和声卡访问,自己看代码分析吧。

Java code
import java.io.File;import java.io.IOException;import java.util.ArrayList;import javax.sound.sampled.AudioFormat;import javax.sound.sampled.AudioInputStream;import javax.sound.sampled.AudioSystem;import javax.sound.sampled.DataLine;import javax.sound.sampled.FloatControl;import javax.sound.sampled.LineUnavailableException;import javax.sound.sampled.SourceDataLine;import javax.sound.sampled.UnsupportedAudioFileException;public class SoundPlayer {         private String filename;     private Position curPosition;     private final int EXTERNAL_BUFFER_SIZE = 524288; // 128Kb     enum Position {        LEFT, RIGHT, NORMAL    };     public SoundPlayer(String wavfile) {        filename = wavfile;        curPosition = Position.NORMAL;    }     public SoundPlayer(String wavfile, Position p) {        filename = wavfile;        curPosition = p;    }     public void run() {         File soundFile = new File(filename);        if (!soundFile.exists()) {            System.err.println("Wave file not found: " + filename);            return;        }         AudioInputStream audioInputStream = null;        try {            audioInputStream = AudioSystem.getAudioInputStream(soundFile);        } catch (UnsupportedAudioFileException e1) {            e1.printStackTrace();            return;        } catch (IOException e1) {            e1.printStackTrace();            return;        }         AudioFormat format = audioInputStream.getFormat();        SourceDataLine auline = null;        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);         try {            auline = (SourceDataLine) AudioSystem.getLine(info);            auline.open(format);        } catch (LineUnavailableException e) {            e.printStackTrace();            return;        } catch (Exception e) {            e.printStackTrace();            return;        }         if (auline.isControlSupported(FloatControl.Type.PAN)) {            FloatControl pan = (FloatControl) auline                    .getControl(FloatControl.Type.PAN);            if (curPosition == Position.RIGHT)                pan.setValue(1.0f);            else if (curPosition == Position.LEFT)                pan.setValue(-1.0f);        }          auline.start();        int nBytesRead = 0;        byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];        try {            while (nBytesRead != -1) {                nBytesRead = audioInputStream.read(abData, 0, abData.length);                if (nBytesRead >= 0)                    auline.write(abData, 0, nBytesRead);            }        } catch (Exception e) {            e.printStackTrace();            return;        } finally {            auline.drain();            auline.close();        }     }    public static void main(String args[]){        SoundPlayer a=new SoundPlayer(文件地址);        a.run();    }} 


[解决办法]
关注

热点排行