【问题标题】:Screeching white sound coming while playing audio as a raw stream将音频作为原始流播放时发出尖锐的白色声音
【发布时间】:2020-08-11 02:22:35
【问题描述】:

我。背景

  1. 我正在尝试制作一个应用程序,它有助于在波形级别、单词级别甚至字符级别非常准确地将字幕与音频波形匹配。
  2. 音频应该是梵文圣歌(瑜伽、仪式等),它们是极长的复合词 [示例 - aṅganyā-sokta-mātaro-bījam 传统上是一个断词,只是为了帮助阅读]
  3. 输入的文字记录/字幕可能在句子/诗歌级别大致同步,但在单词级别肯定不会同步。
  4. 应用程序应该能够找出音频波形中的静音点,以便它可以猜测每个单词(甚至是单词中的字母/辅音/元音)的开始和结束点,这样音频-在单词级别(甚至在字母/辅音/元音级别)的吟唱和视觉字幕完美匹配,并且相应的 UI 只是突出显示或动画显示当时正在吟唱的字幕行中的确切单词(甚至字母) ,并以更大的字体显示该单词(甚至是字母/辅音/元音)。此应用的目的是帮助学习梵文诵经。
  5. 预计它不会是 100% 自动化的过程,也不是 100% 手动的,而是应用程序应尽可能帮助人类的组合。

二。以下是我为此目的编写的第一个代码,其中

  1. 首先我打开一个 mp3(或任何音频格式)文件,
  2. 寻找音频文件时间线中的任意点 // 从零偏移开始播放
  3. 获取原始格式的音频数据有两个目的 - (1) 播放它和 (2) 绘制波形。
  4. 使用标准 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


【解决方案1】:

我怀疑您的尖叫声源于传递给音频系统的半满缓冲区。

如上面的评论所示,我会使用FFSampledSP 之类的东西(如果在 mac 或 Windows 上),然后像下面这样的代码,这更像是 java-esque。

只要确保 FFSampledSP 完整 jar 在您的路径中,您就可以开始了。

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

public class PlayerDemo {

    /**
     * Derive a PCM format.
     */
    private static AudioFormat toSignedPCM(final AudioFormat format) {
        final int sampleSizeInBits = format.getSampleSizeInBits() <= 0 ? 16 : format.getSampleSizeInBits();
        final int channels = format.getChannels() <= 0 ? 2 : format.getChannels();
        final float sampleRate = format.getSampleRate() <= 0 ? 44100f : format.getSampleRate();
        return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                sampleRate,
                sampleSizeInBits,
                channels,
                (sampleSizeInBits > 0 && channels > 0) ? (sampleSizeInBits/8)*channels : AudioSystem.NOT_SPECIFIED,
                sampleRate,
                format.isBigEndian()
        );
    }


    public static void main(final String[] args) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        final File audioFile = new File(args[0]);
        // open mp3 or whatever
        final Long durationInMicroseconds = (Long)AudioSystem.getAudioFileFormat(audioFile).getProperty("duration");
        // how long is the file, use AudioFileFormat properties
        System.out.println("Duration in microseconds (not millis!): " + durationInMicroseconds);
        // open the mp3 stream (not yet decoded)
        final AudioInputStream mp3In = AudioSystem.getAudioInputStream(audioFile);
        // derive a suitable PCM format that can be played by the AudioSystem
        final AudioFormat desiredFormat = toSignedPCM(mp3In.getFormat());
        // ask the AudioSystem for a source line for playback
        // that corresponds to the derived PCM format
        final SourceDataLine line = AudioSystem.getSourceDataLine(desiredFormat);

        // now play, typically in separate thread
        new Thread(() -> {
            final byte[] buf = new byte[4096];
            int justRead;
            // convert to raw PCM samples with the AudioSystem
            try (final AudioInputStream rawIn = AudioSystem.getAudioInputStream(desiredFormat, mp3In)) {
                line.open();
                line.start();
                while ((justRead = rawIn.read(buf)) >= 0) {
                    // only write bytes we really read, not more!
                    line.write(buf, 0, justRead);
                    final long microsecondPosition = line.getMicrosecondPosition();
                    System.out.println("Current position in microseconds: " + microsecondPosition);
                }
            } catch (IOException | LineUnavailableException e) {
                e.printStackTrace();
            } finally {
                line.drain();
                line.stop();
            }
        }).start();
    }
}

常规的 Java API 不允许跳转到任意位置。但是,FFSampledSP 包含一个扩展,即seek() 方法。要使用它,只需将上面示例中的 rawIn 转换为 FFAudioInputStream 并使用 timetimeUnit 调用 seek()

【讨论】:

  • 谢谢,让我试试这个。我会回来的。
  • 玩的很好,现在让我试着看懂代码。
  • 如何寻找和播放任意时间点的音频?正如我所解释的,我需要非常准确地同步音频和字幕,因此用户应该能够准确地做到这一点。我将准确搜索排除在当前问题的范围之外,但是有人如何进行基本搜索?这很重要。谢谢你。旁白:现在我可能需要以图形方式查看音频数据以查看静音点,或者可以通过计算来完成。那将是不同的主题,而不是这个问题。
  • 不,你完全正确。您不会使用 MediaPlayer 获得波形。
  • 您需要将字节转换为样本,即整数或浮点数,然后绘制它们。有关转换的示例代码,请参阅 github.com/hendriks73/jipes/blob/master/src/main/java/com/… 如果这没有帮助,请发布一个新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-21
  • 2020-04-15
  • 1970-01-01
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多