【发布时间】:2020-08-11 02:22:35
【问题描述】:
我。背景
- 我正在尝试制作一个应用程序,它有助于在波形级别、单词级别甚至字符级别非常准确地将字幕与音频波形匹配。
- 音频应该是梵文圣歌(瑜伽、仪式等),它们是极长的复合词 [示例 - aṅganyā-sokta-mātaro-bījam 传统上是一个断词,只是为了帮助阅读]
- 输入的文字记录/字幕可能在句子/诗歌级别大致同步,但在单词级别肯定不会同步。
- 应用程序应该能够找出音频波形中的静音点,以便它可以猜测每个单词(甚至是单词中的字母/辅音/元音)的开始和结束点,这样音频-在单词级别(甚至在字母/辅音/元音级别)的吟唱和视觉字幕完美匹配,并且相应的 UI 只是突出显示或动画显示当时正在吟唱的字幕行中的确切单词(甚至字母) ,并以更大的字体显示该单词(甚至是字母/辅音/元音)。此应用的目的是帮助学习梵文诵经。
- 预计它不会是 100% 自动化的过程,也不是 100% 手动的,而是应用程序应尽可能帮助人类的组合。
二。以下是我为此目的编写的第一个代码,其中
- 首先我打开一个 mp3(或任何音频格式)文件,
- 寻找音频文件时间线中的任意点 // 从零偏移开始播放
- 获取原始格式的音频数据有两个目的 - (1) 播放它和 (2) 绘制波形。
- 使用标准 Java 音频库播放原始音频数据
三。我面临的问题是,每个循环之间都有刺耳的声音。
- 可能我需要关闭循环之间的界限?听起来很简单,我可以试试。
- 但我也想知道这种整体方法本身是否正确?任何提示、指南、建议、链接都会非常有帮助。
- 另外,我只是硬编码了采样率等(44100Hz 等),这些设置为默认预设好还是应该取决于输入格式?
四。这是代码
import com.github.kokorin.jaffree.StreamType;
import com.github.kokorin.jaffree.ffmpeg.FFmpeg;
import com.github.kokorin.jaffree.ffmpeg.FFmpegProgress;
import com.github.kokorin.jaffree.ffmpeg.FFmpegResult;
import com.github.kokorin.jaffree.ffmpeg.NullOutput;
import com.github.kokorin.jaffree.ffmpeg.PipeOutput;
import com.github.kokorin.jaffree.ffmpeg.ProgressListener;
import com.github.kokorin.jaffree.ffprobe.Stream;
import com.github.kokorin.jaffree.ffmpeg.UrlInput;
import com.github.kokorin.jaffree.ffprobe.FFprobe;
import com.github.kokorin.jaffree.ffprobe.FFprobeResult;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
public class FFMpegToRaw {
Path BIN = Paths.get("f:\\utilities\\ffmpeg-20190413-0ad0533-win64-static\\bin");
String VIDEO_MP4 = "f:\\org\\TEMPLE\\DeviMahatmyamRecitationAudio\\03_01_Devi Kavacham.mp3";
FFprobe ffprobe;
FFmpeg ffmpeg;
public void basicCheck() throws Exception {
if (BIN != null) {
ffprobe = FFprobe.atPath(BIN);
} else {
ffprobe = FFprobe.atPath();
}
FFprobeResult result = ffprobe
.setShowStreams(true)
.setInput(VIDEO_MP4)
.execute();
for (Stream stream : result.getStreams()) {
System.out.println("Stream " + stream.getIndex()
+ " type " + stream.getCodecType()
+ " duration " + stream.getDuration(TimeUnit.SECONDS));
}
if (BIN != null) {
ffmpeg = FFmpeg.atPath(BIN);
} else {
ffmpeg = FFmpeg.atPath();
}
//Sometimes ffprobe can't show exact duration, use ffmpeg trancoding to NULL output to get it
final AtomicLong durationMillis = new AtomicLong();
FFmpegResult fFmpegResult = ffmpeg
.addInput(
UrlInput.fromUrl(VIDEO_MP4)
)
.addOutput(new NullOutput())
.setProgressListener(new ProgressListener() {
@Override
public void onProgress(FFmpegProgress progress) {
durationMillis.set(progress.getTimeMillis());
}
})
.execute();
System.out.println("audio size - "+fFmpegResult.getAudioSize());
System.out.println("Exact duration: " + durationMillis.get() + " milliseconds");
}
public void toRawAndPlay() throws Exception {
ProgressListener listener = new ProgressListener() {
@Override
public void onProgress(FFmpegProgress progress) {
System.out.println(progress.getFrame());
}
};
// code derived from : https://stackoverflow.com/questions/32873596/play-raw-pcm-audio-received-in-udp-packets
int sampleRate = 44100;//24000;//Hz
int sampleSize = 16;//Bits
int channels = 1;
boolean signed = true;
boolean bigEnd = false;
String format = "s16be"; //"f32le"
//https://trac.ffmpeg.org/wiki/audio types
final AudioFormat af = new AudioFormat(sampleRate, sampleSize, channels, signed, bigEnd);
final DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
final SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
line.open(af, 4096); // format , buffer size
line.start();
OutputStream destination = new OutputStream() {
@Override public void write(int b) throws IOException {
throw new UnsupportedOperationException("Nobody uses thi.");
}
@Override public void write(byte[] b, int off, int len) throws IOException {
String o = new String(b);
boolean showString = false;
System.out.println("New output ("+ len
+ ", off="+off + ") -> "+(showString?o:""));
// output wave form repeatedly
if(len%2!=0) {
len -= 1;
System.out.println("");
}
line.write(b, off, len);
System.out.println("done round");
}
};
// src : http://blog.wudilabs.org/entry/c3d357ed/?lang=en-US
FFmpegResult result = FFmpeg.atPath(BIN).
addInput(UrlInput.fromPath(Paths.get(VIDEO_MP4))).
addOutput(PipeOutput.pumpTo(destination).
disableStream(StreamType.VIDEO). //.addArgument("-vn")
setFrameRate(sampleRate). //.addArguments("-ar", sampleRate)
addArguments("-ac", "1").
setFormat(format) //.addArguments("-f", format)
).
setProgressListener(listener).
execute();
// shut down audio
line.drain();
line.stop();
line.close();
System.out.println("result = "+result.toString());
}
public static void main(String[] args) throws Exception {
FFMpegToRaw raw = new FFMpegToRaw();
raw.basicCheck();
raw.toRawAndPlay();
}
}
谢谢
【问题讨论】:
-
如果您使用的是 macOS 或 Windows,您可能需要考虑使用 tagtraum.com/ffsampledsp 以使其更加优雅。
-
@Hendrik - 任何示例代码的链接?那会有所帮助。感谢您的评论。
-
通过读入一个已知音频频率为 100 赫兹的文件进行简化,并通过打印 PCM 格式的原始音频曲线(只是音频曲线上的点)来确认您的代码工作正常,这样您就可以看到音频曲线数据点根据正弦曲线向上/向下变化......这将让您确认您的代码是可靠的
-
@ScottStensland - 感谢您的评论。我可以听到音频亮起,它播放正常,然后发出刺耳的声音,然后下一个循环,播放正常,然后发出刺耳的声音。仍在解决问题。
标签: java audio ffmpeg javasound