前言
查看了相关文章然后一笔一笔打代码再调试成功出结果,
eguid的博客
不保证代码能够原封不动就能运行,
这里做一下记录。
ps:代码内容有改动,原版的可以看原作者的。
代码
package net.w2p.JCVStudio.zhiboStudy;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FrameRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/***
* 音频录制以及推流--推到rtmp或者直接保存到本地。
* https://blog.csdn.net/eguid_1/article/details/52702385
* 录制音频(录制麦克风)到本地文件/流媒体服务器(基于javax.sound、javaCV-FFMPEG)
* **/
public class Lesson05 {
static final ConcurrentHashMap<String,Object> map4Middel=new ConcurrentHashMap<>(3);
Logger logger= LoggerFactory.getLogger("第五课");
/**
* 设置音频编码器 最好是系统支持的格式,否则getLine() 会发生错误
* 采样率:44.1k;采样率位数:16位;立体声(stereo);是否签名;true:
* big-endian字节顺序,false:little-endian字节顺序(详见:ByteOrder类)
*/
private Runnable recordTask(final String outputUrl) throws Exception{
final int AUDIO_DEVICE_INDEX=0;
AudioFormat audioFormat=new AudioFormat(44100.0F,16,2,true,false);
logger.info("准备开启音频...");
// 通过AudioSystem获取本地音频混合器信息
Mixer.Info[] minfoSet= AudioSystem.getMixerInfo();
// 通过AudioSystem获取本地音频混合器
Mixer mixer=AudioSystem.getMixer(minfoSet[AUDIO_DEVICE_INDEX]);
// 通过设置好的音频编解码器获取数据线信息
DataLine.Info dataLineInfo=new DataLine.Info(TargetDataLine.class,audioFormat);
// 打开并开始捕获音频
// 通过line可以获得更多控制权
// 获取设备:TargetDataLine line
// =(TargetDataLine)mixer.getLine(dataLineInfo);
Line dataline=null;
try{
dataline=AudioSystem.getLine(dataLineInfo);
}
catch (Exception ed){
ed.printStackTrace();
logger.info("开启失败");
return null;
}
TargetDataLine line=(TargetDataLine)dataline;
try{
line.open(audioFormat);
}
catch (Exception ed){
line.stop();
ed.printStackTrace();
try{
line.open(audioFormat);
}
catch (Exception ed2){
line.stop();
ed2.printStackTrace();
logger.info("按照指定音频解码器打开失败,程序终止。");
return null;
}
}
line.start();
logger.info("已经成功开启音频...");
// 获得当前音频采样率
int sampleRate=(int)audioFormat.getSampleRate();
// 获取当前音频通道数量
int numChannels=audioFormat.getChannels();
// 初始化音频缓冲区(size是音频采样率*通道数)
int audioBufferSize=sampleRate*numChannels;
byte[] audioBytes=new byte[audioBufferSize];
logger.info("音轨数量:{}",numChannels);
Runnable crabAudio=new Runnable() {
ShortBuffer sBuff=null;
int nBytesRead;
int nSamplesRead;
@Override
public void run() {
if(Thread.interrupted()){
logger.info("线程已经关闭了,无须执行其他操作。");
return;
}
if(map4Middel.get("stop")!=null){
logger.info("已经停止录音");
FFmpegFrameRecorder recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
if(recorder!=null){
try{
recorder.stop();
recorder.release();
}
catch (Exception ed){
ed.printStackTrace();
}
}
return;
}
logger.info("读取音频数据...");
// 非阻塞方式读取
nBytesRead=line.read(audioBytes,0,line.available());
// 因为我们设置的是16位音频格式,所以需要将byte[]转成short[]
nSamplesRead=nBytesRead/2;
short[] samples=new short[nSamplesRead];
/**
* ByteBuffer.wrap(audioBytes)-将byte[]数组包装到缓冲区
* ByteBuffer.order(ByteOrder)-按little-endian修改字节顺序,解码器定义的
* ByteBuffer.asShortBuffer()-创建一个新的short[]缓冲区
* ShortBuffer.get(samples)-将缓冲区里short数据传输到short[]
*/
ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
sBuff=ShortBuffer.wrap(samples,0,nSamplesRead);
// 按通道录制shortBuffer
FFmpegFrameRecorder recorder=null;
if(map4Middel.get("recorder")==null){
recorder=new FFmpegFrameRecorder(outputUrl,1920,1080,2);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264,编码
// recorder.setFormat("flv");
recorder.setFormat("flv");//封装格式,如果是推送到rtmp就必须是flv封装格式
recorder.setPixelFormat(0);
recorder.setFrameRate(25);
recorder.setInterleaved(true);
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoOption("tune", "zerolatency");
recorder.setVideoOption("fflags", "nobuffer");
recorder.setVideoOption("analyzeduration", "0");
/**添加audio相关参数**/
// 不可变(固定)音频比特率
recorder.setAudioOption("crf", "0");
// 最高质量
recorder.setAudioQuality(0);
// 音频比特率
recorder.setAudioBitrate(192000);
// 音频采样率
recorder.setSampleRate(44100);
// 双通道(立体声)
recorder.setAudioChannels(numChannels);
// 音频编/解码器
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
/**添加audio相关参数 end**/
//
map4Middel.put("recorder",recorder);
try {
recorder.start();
}
catch (Exception ed){
ed.printStackTrace();
}
}
else{
recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
}
try{
logger.info("录制音频数据.....");
recorder.recordSamples(sampleRate,numChannels,sBuff);
}
catch (Exception ed){
ed.printStackTrace();
}
}
@Override
protected void finalize() throws Throwable {
sBuff.clear();
sBuff = null;
super.finalize();
}
};
return crabAudio;
}
public void recordSound(String outputUrl) throws Exception{
int FRAME_RATE=25;
final ScheduledThreadPoolExecutor exec=new ScheduledThreadPoolExecutor(1);
Runnable crabAudio=recordTask(outputUrl);
ScheduledFuture tasker=exec.scheduleAtFixedRate(crabAudio,0,(long)1000/FRAME_RATE, TimeUnit.MILLISECONDS);
//使用java的JFrame显示图像
CanvasFrame mainFrame = new CanvasFrame("",
CanvasFrame.getDefaultGamma()/2.2);
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setTitle("录音测试");
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JLabel jl=new JLabel("这是使用JFrame类创建的窗口"); //创建一个标签
Container c=mainFrame.getContentPane(); //获取当前窗口的内容窗格
c.add(jl); //将标签组件添加到内容窗格上
mainFrame.setVisible(true);
mainFrame.setSize(200,200);
mainFrame.setAlwaysOnTop(true);
mainFrame.show();
mainFrame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
//在这里面写你要做的操作比如关闭服务器链接
System.out.println("关闭窗口");
map4Middel.put("stop",true);
FFmpegFrameRecorder recorder=(FFmpegFrameRecorder)map4Middel.get("recorder");
if(recorder!=null){
try{
recorder.stop();
recorder.release();
}
catch (Exception ed){
ed.printStackTrace();
}
logger.info("推流结束---》整理成功");
}
tasker.cancel(true);
if(!exec.isShutdown()){
exec.shutdownNow();
}
}
});
}
public void record(String outputUrl) throws Exception {
final int DEVICE_INDEX=0;
// 44.1 sample rate, 16 bits, stereo, signed, little endian
AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);
Mixer.Info[] minfoSet = AudioSystem.getMixerInfo();
Mixer mixer = AudioSystem.getMixer(minfoSet[DEVICE_INDEX]);
DataLine.Info dataLineInfo=new DataLine.Info(TargetDataLine.class,audioFormat);
// Open and start capturing audio
Line dataline=null;
try{
dataline=AudioSystem.getLine(dataLineInfo);
}
catch (Exception ed){
ed.printStackTrace();
logger.info("开启失败");
return;
}
TargetDataLine line=(TargetDataLine)dataline;
line.open(audioFormat);
line.start();
// 获得当前音频采样率
int sampleRate=(int)audioFormat.getSampleRate();
// 获取当前音频通道数量
int numChannels=audioFormat.getChannels();
// 初始化音频缓冲区(size是音频采样率*通道数)
int audioBufferSize = (int) audioFormat.getSampleRate() * audioFormat.getChannels();
byte[] audioBytes = new byte[audioBufferSize];
AudioInputStream isS = new AudioInputStream(line);
FFmpegFrameRecorder recorder=new FFmpegFrameRecorder(outputUrl,
numChannels);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264,编码
// recorder.setFormat("flv");
recorder.setFormat("flv");//封装格式,如果是推送到rtmp就必须是flv封装格式
recorder.setPixelFormat(0);
recorder.setFrameRate(25);
// recorder.setVideoOption("preset", "ultrafast");
// recorder.setVideoOption("tune", "zerolatency");
// recorder.setVideoOption("fflags", "nobuffer");
// recorder.setVideoOption("analyzeduration", "0");
//
try {
recorder.start();
}
catch (Exception ed){
ed.printStackTrace();
}
//使用java的JFrame显示图像
CanvasFrame mainFrame = new CanvasFrame("",
CanvasFrame.getDefaultGamma()/2.2);
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setTitle("录音测试");
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JLabel jl=new JLabel("这是使用JFrame类创建的窗口"); //创建一个标签
Container c=mainFrame.getContentPane(); //获取当前窗口的内容窗格
c.add(jl); //将标签组件添加到内容窗格上
mainFrame.setVisible(true);
mainFrame.setSize(200,200);
mainFrame.setAlwaysOnTop(true);
mainFrame.show();
mainFrame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
//在这里面写你要做的操作比如关闭服务器链接
System.out.println("关闭窗口");
map4Middel.put("stop",true);
try {
recorder.stop();
recorder.release();
logger.info("推流结束");
}
catch (Exception ed){
ed.printStackTrace();
}
}
});
while (!Thread.interrupted()&&map4Middel.get("stop")==null)
{
try
{
// Read from the line... non-blocking
// int nBytesRead = isS.read(audioBytes, 0, line.available());
int nBytesRead = line.read(audioBytes, 0, line.available());
if (nBytesRead > 0)
{
// Since we specified 16 bits in the AudioFormat, we need to convert our read byte[] to short[] (see source from FFmpegFrameRecorder.recordSamples for AV_SAMPLE_FMT_S16)
// Let's initialize our short[] array
int nSamplesRead = nBytesRead / 2;
short[] samples = new short[nSamplesRead];
// Let's wrap our short[] into a ShortBuffer and pass it to recordSamples
ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
logger.info("采样率等数据:nBytesRead:{},nSamplesRead:{}",nBytesRead,nSamplesRead);
ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead);
// recorder is instance of org.bytedeco.javacv.FFmpegFrameRecorder
recorder.recordSamples((int) audioFormat.getSampleRate(), audioFormat.getChannels(), sBuff);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
/**
* 本次内容实现了,普通视频-》推送到直播流
* 直播流-》保存到本地
* 直播流-》转推其他直播流
* **/
Lesson05 test=new Lesson05();
final String outFile="/home/too-white/temp/lesson05_output.flv";
final String rtmpUrl="rtmp://localhost/live/livestream";
/**
* 推荐使用的rtmp直播平台
* https://blog.csdn.net/zhichaosong/article/details/89053009
* **/
// final String rtmpOnlineUrl="rtmp://202.69.69.180:443/webcast/bshdlive-pc";
// test.frameRecord(inputFile,outFile,1);
// test.frameRecord(inputFile,rtmpUrl,1);
// test.frameRecord(rtmpOnlineUrl,outFile,1);
// test.recordSound(outFile);
// test.recordSound(outFile);
//--推送到rtmp上面--备注,本地保存可以,但是推送到流媒体一直不成功。。需要核查一下。
test.recordSound(rtmpUrl);
// test.recordSound(outFile);
}
}