Gagravarr/VorbisJava

Not an issue - Looking for an example of writing encoded Opus data to a file

Opened this issue ยท 19 comments

I have opus encoded bytes and I'd like to write them to an opus file; since the only example I found uses an existing opus file to read from then write to a new file, it doesn't fit my need. I don't have a source file, I am taking pcm samples and encoding them with an Opus encoder, so I tried to do this and it didn't work:

    OggPacket pkt = new OggPacket((byte[]) outBuf.getData());
    OpusAudioData oad = new OpusAudioData(pkt);
    opusFile.writeAudioData(oad);

As well as this

    OpusAudioData oad = new OpusAudioData((byte[]) outBuf.getData());
    opusFile.writeAudioData(oad);

When I tried using the OggPacket, I get this exception:

java.lang.NullPointerException: null
	at org.gagravarr.ogg.OggPacket.getGranulePosition(OggPacket.java:74)
	at org.gagravarr.ogg.OggStreamAudioVisualData.<init>(OggStreamAudioVisualData.java:28)
	at org.gagravarr.ogg.OggStreamAudioData.<init>(OggStreamAudioData.java:21)
	at org.gagravarr.opus.OpusAudioData.<init>(OpusAudioData.java:29)

My file creation looks like so:

        Path opus = Paths.get("target/test-classes/out.opus");
        // clean up existing output files
        try {
            Files.deleteIfExists(opus);
        } catch (IOException e1) {
        }
        OpusFile opusFile = null;
        try {
            // set opus out
            OpusTags ot = new OpusTags();
            OpusInfo oi = new OpusInfo();
            oi.setSampleRate(48000);
            oi.setNumChannels(2);
            //oi.preSkip = 3840
            opusFile = new OpusFile(new FileOutputStream(opus.toFile()), oi, ot);

Any examples or clues as to how to accomplish what I need would be greatly appretiated.

My main focus in writing the library was on metadata, the audio support was mostly just for copying between files when changing metadata. That means functionality and examples in the area you want to use is lacking, sorry!

I think you need to be creating a new OpusAudioData for every few encoded samples, from bytes, and writing them individually. That way, the granule positions should get calculated for you.

Only create via packets if the encoder is doing the framing stuff for you, and giving you the granule data, in which case you'd also need to record the granule info on the packet before you wrap as AudioData

@Gagravarr thanks for the info; I was wondering what the heck I was doing wrong. I'll check my encoded bytes and see if there is information in there; I'm not super familiar with the Opus byte stream at a low level yet.

andrm commented

@mondain Opus works on a frame level, one frame usually 20ms. This data is packaged into the Ogg container format.
See the RFC section 3 (https://tools.ietf.org/html/rfc6716#section-3) , it explains how it is done. If you have encoded Opus frames, you can use this library to generate an Opus file. Encoding PCM is beyond the scope of this library.

I've added a unit test for opus which adds audio data to a new file, a moderate amount of data at a time (to mostly replicate a frame), after a bug fix it seems to be working -

public void testWriteAudio() throws Exception {

@Gagravarr awesome! Thanks for that new test; I was just reading through the RFC sections that @andrm had posted earlier.

If you wanted to add actual opus encode of dummy pcm values, there is a pure java opus encoder here without any JNI requirements. Your unit test helped me figure out what I had done wrong, I was providing too much data to the opus file and not utilizing my offset value.

andrm commented

@mondain Thanks for the link, I didn't know about that!

I thought it might be found as interesting, I learned of it just recently as well; I'm using the official opus native stuff via JNI for regular work.

I pasted an opus encoded tone as hex strings to this file https://pastebin.com/jPRiwELv Its from a source that I'm testing with. When I write it to opus, it has a blip or click after what I think is each OpusAudioData write, but I'm not sure where its being introduced yet. I'll write a unit test and put it in a PR today, maybe someone can help me sort it out. I've linked the output files here https://www.dropbox.com/sh/hyehy3agw05hamj/AAAWrv_3VjRgMKRh0OSqelr6a?dl=0 out.opus is the opus output of course, out.wav is the AAC version.

andrm commented

Sorry, I don't have time to look at it. Please use opusinfo from the opus utilities with your generated file, it will tell you if something went wrong.

@Gagravarr and @andrm don't misunderstand me, I appreciate your assistance with my reports; I also maintain a few projects so I've been on the other end of this; I demand nothing, if you are able to assist then that's super! If not, no big deal, I'll dig in as needed.

I fixed a unit test bug in TestOpusFileWriter, I'll have a PR by EOD today for you guys to have a look at; also "resolved" the javadoc issue by fixing some of the tags and setting a configuration option on the plugin itself.

Added my PR #41

I suspect there's an issue when writing to the opus file for stereo content. My source is 00:00:01.00 long and 3001 160 byte chunks; once written it shows up as 00:00:01.56 long (with ffprobe). I ran the file through opus tool, it prints a bunch of granule error/warn items; I'm not sure what to make of the output:

New logical stream 72cf (29391) found
Processing file "../core/target/test-classes/out.opus"

Opus Headers:
  Version: 1
  Vendor: Gagravarr.org Java Vorbis Tools v0.8 20160217
  Channels: 2
  Rate: 48000Hz
  Pre-Skip: 120
  Playback Gain: 0dB

User Comments:
  title=Test Dummy Audio

WARNING: Sample count ahead granule (146880<0) in stream29391
WARNING: Sample count ahead granule (245760<0) in stream29391
WARNING: Sample count ahead granule (344640<0) in stream29391
WARNING: Sample count ahead granule (443520<0) in stream29391
WARNING: Sample count ahead granule (542400<0) in stream29391
WARNING: Sample count ahead granule (641280<0) in stream29391
WARNING: Sample count ahead granule (740160<0) in stream29391
WARNING: Sample count ahead granule (839040<0) in stream29391
WARNING: Sample count ahead granule (937920<0) in stream29391
WARNING: Sample count ahead granule (1036800<0) in stream29391
WARNING: Sample count ahead granule (1135680<0) in stream29391
WARNING: Sample count ahead granule (1234560<0) in stream29391
WARNING: Sample count ahead granule (1333440<0) in stream29391
WARNING: Sample count ahead granule (1432320<0) in stream29391
WARNING: Sample count ahead granule (1531200<0) in stream29391
WARNING: Sample count ahead granule (1630080<0) in stream29391
WARNING: Sample count ahead granule (1728960<0) in stream29391
WARNING: Sample count ahead granule (1827840<0) in stream29391
WARNING: Sample count ahead granule (1926720<0) in stream29391
WARNING: Sample count ahead granule (2025600<0) in stream29391
WARNING: Sample count ahead granule (2124480<0) in stream29391
WARNING: Sample count ahead granule (2223360<0) in stream29391
WARNING: Sample count ahead granule (2322240<0) in stream29391
WARNING: Sample count ahead granule (2421120<0) in stream29391
WARNING: Sample count ahead granule (2520000<0) in stream29391
WARNING: Sample count ahead granule (2618880<0) in stream29391
WARNING: Sample count ahead granule (2717760<0) in stream29391
Logical stream 72cf (29391) completed

Opus Audio:
  Total Data Packets: 3001
  Total Data Length: 480160
  Audio Length Seconds: 1.5575
  Audio Length: 00:00:01.56
  Packet duration:     20.0ms (max),     20.0ms (avg),     20.0ms (min)
  Page duration:     3400.0ms (max),   2069.7ms (avg),   1000.0ms (min)
  Total data length: 480160 (overhead: 0.64%)
  Playback length: 00:00:01.56
  Average bitrate: 2.37 mb/s, w/o overhead: 2.35 mb/s (hard-CBR)

Also I've tried with preskip of 0 or 120, doesn't seem to matter.

andrm commented

@mondain Please see https://tools.ietf.org/html/rfc7845.html on how to calculate granule position and pre-skip.

core/src/main/java/org/gagravarr/opus/OpusStatistics.java has also instructive code, you see where the errors are coming from.

@andrm I pulled the PR #33 into my fork and the granule warnings disappeared and the duration is exactly correct now in ffprobe and opustool Playback length: 00:01:00.00

@mondain do you think you could post a more complete solution to your issue? From what I gathered I'm trying to do the same thing and I tried using Vorbis in the same way you're doing and it's not working. See also my post here

thanks

I also encounter this problem.
I can encode a pcm file for each sub byte array to opus byte array. Those encoded byte array are merged into one big byte array and saved a file. When encoding, I also recorded the relationship from original size to encoded size. ex: 320 byte to 17 byte. With that relationship. I can decode the 17 byte to original 320 byte.

However, the opusfile requires a magic header. I don't not know how to add header for the merged encoded byte array.
The mergedByteArray size is 25200 and the size of output file with the following code is 25435.

        File file = new File(fileFolder + filename + ".opus");
        FileOutputStream outputStream = new FileOutputStream(file);

        OpusInfo info = new OpusInfo();
        info.setNumChannels(1);
        info.setSampleRate(16000);
        OpusTags tags = new OpusTags();
        OpusFile oFile = new OpusFile(outputStream, info, tags);
        OpusAudioData data = new OpusAudioData(mergedByteArray);
        oFile.writeAudioData(data);
        oFile.close();

I upload the output file into https://www.opusplayer.net/ and it cannot recognized my oupus file.