Skip to main content

Topic: Opus inside OggPages (Read 10213 times) previous topic - next topic

0 Members and 1 Guest are viewing this topic.
  • thinktink
  • [*]
Opus inside OggPages
I'm currently working on a Winamp input plugin to provide better native support for the Opus codec than the one that dshow could provide linking in the k-lite codec pack.  Metadata and whatnot.

Anyways, I'm dynamically linking in the official opusfile-0.1-win32 libraries from the opus-codec site and I have been able to succesfully decode and get Winamp to play a local .opus file.  However, I've not been so fortunate with online streams.

I am not familiar at all with any of the internals/packets/pages/whatevers of live streams.  I can get my plugin to connect to a server, get the data it's outchucking, parse out the HTTP header, parse the seperate Ogg Pages, get the OpusHead, and get the OpusTags, but that's it.  I tried sending, just the Ogg pages, a chain of Ogg pages, individual segments in the pages, to all of the in-memory functions of the libraries but they all either return error code -133 (OP_EBADHEADER), -137 (OP_EBADLINK), or -139 (OP_EBADTIMESTAMP).

I know nothing of "granules", whatever that means.

Can somebody tell me how to decode an Ogg Opus stream with those libraries or if it's not possible to?

C++

tia
  • Last Edit: 14 November, 2012, 12:19:06 AM by thinktink

  • NullC
  • [*][*][*]
  • Developer
Opus inside OggPages
Reply #1
I am not familiar at all with any of the internals/packets/pages/whatevers of live streams.  I can get my plugin to connect to a server, get the data it's outchucking, parse out the HTTP header, parse the seperate Ogg Pages, get the OpusHead, and get the OpusTags, but that's it.  I tried sending, just the Ogg pages, a chain of Ogg pages, individual segments in the pages, to all of the in-memory functions of the libraries but they all either return error code -133 (OP_EBADHEADER), -137 (OP_EBADLINK), or -139 (OP_EBADTIMESTAMP).

Live streams are exactly like files. You shouldn't need to do any parsing, you should be able to pass the data to opusfile using the callback apis.

Opusfile has good http support internally, but unfortunately its POSIX-api only at this point so it doesn't include that functionality on Windows. I think it would be great if someone wanted to work on that.

Quote
Can somebody tell me how to decode an Ogg Opus stream with those libraries or if it's not possible to?

Can you post your code?  It's easier to point out problems in existing code then to walk over the whole process and hopefully clarify something for you by accident.
  • Last Edit: 15 November, 2012, 09:55:09 AM by NullC

  • thinktink
  • [*]
Opus inside OggPages
Reply #2
I am not familiar at all with any of the internals/packets/pages/whatevers of live streams.  I can get my plugin to connect to a server, get the data it's outchucking, parse out the HTTP header, parse the seperate Ogg Pages, get the OpusHead, and get the OpusTags, but that's it.  I tried sending, just the Ogg pages, a chain of Ogg pages, individual segments in the pages, to all of the in-memory functions of the libraries but they all either return error code -133 (OP_EBADHEADER), -137 (OP_EBADLINK), or -139 (OP_EBADTIMESTAMP).
Live streams are exactly like files. You shouldn't need to do any parsing, you should be able to pass the data to opusfile using the callback apis.

Opusfile has good http support internally, but unfortunately its POSIX-api only at this point so it doesn't include that functionality on Windows. I think it would be great if someone wanted to work on that.

Quote
Can somebody tell me how to decode an Ogg Opus stream with those libraries or if it's not possible to?
Can you post your code?  It's easier to point out problems in existing code then to walk over the whole process and hopefully clarify something for you by accident.
Code: [Select]
#include "pshpack1.h"
struct OggHead
{
       char MagicNumber[4];
       BYTE Version;
       BYTE HeaderType;//bit 0=Segment Cont from prev page; bit 1=Beginning of stream; bit 2=End of stream
       unsigned __int64 GranulePosition;
       DWORD BitStreamSerialNumber;
       DWORD PageSequenceNumber;
       DWORD CheckSum;
       BYTE PageSegments;
};
struct SuperOggHead
{
       OggHead oh;
       BYTE SegmentTable[255];
};
#include "poppack.h"
//---------------------------------------------------------------------------
void __fastcall TPlaybackThread::ProcPlayURL(Messages::TMessage & Msg)
{
// Plugin specific code snipped...
AnsiString Get="GET /"+URL+" HTTP/1.0\r\n";
Get+="Host: "+Host+":"+Port+"\r\n";
Get+="Server: "+Host+":"+Port+"\r\n";
Get+="User-Agent: Winamp (Experimental BogProg Opus Support)\r\n";
Get+="\r\n";
ProcessMessages();
AnsiString Res;
TWinSocketStream *pStream=NULL;
char *buff=NULL;
try
{
       cs->Open();
       if(cs->Active)
       {
               buff=new char[65535];
               int sr;
               pStream = new TWinSocketStream(cs->Socket,Timeout);
               pStream->WaitForData(0);
               pStream->TimeOut=Timeout;
               pStream->Write(Get.c_str(),Get.Length());
               int tto=0;
               bool ready=false;
               while(tto<Timeout && !ready && !WantStop)
               {
                       if(!pStream->WaitForData(100))
                       {
                               tto+=100;
                       }
                       else{ready=true;break;}
               }
               while(ready && !WantStop)
               {
                       sr=pStream->Read(buff,65535);
                       if(sr<=0)break;
                       if(!URLData(buff,sr)){break;}
                       if(sr<0)break;
                       ready=false;tto=0;
while(tto<Timeout && !ready && !WantStop)
{
if(!pStream->WaitForData(100))
{
tto+=100;
}
else{ready=true;break;}
}
               }
               if(!ready)ErrorStop();
               delete pStream;
               cs->Close();
               delete [] buff;
       }
}catch(...)
{
       try{if(pStream)delete pStream;}catch(...){}
       try{cs->Close();}catch(...){}
       try{if(buff)delete [] buff;}catch(...){}
       //try{}catch(...){}
       //try{}catch(...){}
       ErrorStop();
}
delete cs;
URLDataStage=0;
// Plugin specific code snipped...
}
//---------------------------------------------------------------------------
bool __fastcall TPlaybackThread::URLData(char * buff, int len)
{
switch(URLDataStage)
{
       case 0://Get the stream type, info and data start
       URLDataStreamHead(buff,len);
       break;
       case 1:
       URLDataStreamData(buff,len);
       break;
}
return !WantStop;
}
//---------------------------------------------------------------------------
void __fastcall TPlaybackThread::URLDataStreamHead(char * buff, int len)
{
char *la=buff;
int lal=len;
if(URLPrev)
{
       char *ack=new char[URLPrevLen+len];
       memcpy(ack,URLPrev,URLPrevLen);
       delete [] URLPrev;
       URLPrev=ack;
       ack=URLPrev+URLPrevLen;
       URLPrevLen=URLPrevLen+len;
       memcpy(ack,buff,len);
       la=URLPrev;
       lal=URLPrevLen;
}
int ds=-1;
for(int i=0;i<lal-3;i++)
{
       if(la[i]=='\r')
       {
               if(la[i+1]=='\n')
               {
                       if(la[i+2]=='\r')
                       {
                               if(la[i+3]=='\n')
                               {
                                       //Found the http head end
                                       ds=i+4;
                                       URLHead.SetLength(ds);
                                       memcpy(URLHead.c_str(),la,ds);
                                       URLDataStage=1;
                                       if(la==URLPrev)
                                       {
                                               int nl=URLPrevLen-ds;
                                               if(nl>0)
                                               {
                                                       char *ack=new char[nl];
                                                       memcpy(ack,URLPrev+ds,nl);
                                                       delete [] URLPrev;
                                                       URLPrev=ack;
                                                       URLPrevLen=nl;
                                               }
                                               else
                                               {
                                                       delete [] URLPrev;URLPrev=0;
                                                       URLPrevLen=0;
                                               }
                                       }
                                       else
                                       {
                                               int nl=lal-ds;
                                               if(nl>0)
                                               {
                                                       char *ack=new char[nl];
                                                       memcpy(ack,la+ds,nl);
                                                       delete [] URLPrev;
                                                       URLPrev=ack;
                                                       URLPrevLen=nl;
                                               }
                                               else
                                               {
                                                       if(URLPrev)delete [] URLPrev;
                                                       URLPrev=NULL;
                                                       URLPrevLen=0;
                                               }
                                       }
                                       return;
                               }
                       }
               }
       }
}
if(URLPrev==NULL)
{
       URLPrev=new char[lal];
       URLPrevLen=lal;
       memcpy(URLPrev,buff,lal);
}
}
//---------------------------------------------------------------------------
void __fastcall TPlaybackThread::URLDataStreamData(char * buff, int len)
{
char *la=buff;
int lal=len;
if(URLPrev)
{
       char *ack=new char[URLPrevLen+len];
       memcpy(ack,URLPrev,URLPrevLen);
       delete [] URLPrev;
       URLPrev=ack;
       ack=URLPrev+URLPrevLen;
       URLPrevLen=URLPrevLen+len;
       memcpy(ack,buff,len);
       la=URLPrev;
       lal=URLPrevLen;
}
{
       OggHead *oh=(OggHead *)la;
       if(lal>=sizeof(OggHead))
       {
               if(
                       oh->MagicNumber[0]!='O' ||
                       oh->MagicNumber[1]!='g' ||
                       oh->MagicNumber[2]!='g' ||
                       oh->MagicNumber[3]!='S' ||
                       oh->Version!=0
               )
               {
                       ErrorStop();
                       return;
               }
               if(lal>sizeof(OggHead))
               {
                       if(URLPrev==NULL)
                       {
                               AddURLPrevBytes(la,lal);
                               la=URLPrev;
                       }
                       SuperOggHead *soh=(SuperOggHead *)la;
                       int nps=soh->oh.PageSegments;
                       int pl=sizeof(OggHead)+nps;
                       if(lal<=pl)
                       {
                               return;
                       }
                       for(int i=0;i<nps;i++)
                       {
                               pl+=soh->SegmentTable[i];
                       }
                       if(lal<=pl)
                       {
                               return;
                       }
                       DecodeOggPage(la,pl);
                       DelURLPrevBytes(pl);
                       la=URLPrev;
                       lal=URLPrevLen;
               }
       }
}
if(URLPrev==NULL)
{
       URLPrev=new char[lal];
       URLPrevLen=lal;
       memcpy(URLPrev,buff,lal);
}
}
//---------------------------------------------------------------------------
void __fastcall TPlaybackThread::DecodeOggPage(char * buff, int len)
{
struct SomethingToDoWithOpus
{
       char h[8];
};
OpusHead oh;memset(&oh,0,sizeof(oh));
SuperOggHead *soh=(SuperOggHead *)buff;
int nps=soh->oh.PageSegments;
int ss=sizeof(OggHead)+nps;
for(int i=0;((i<nps) && (!WantStop));i++)
{
       char *seg=buff+ss;
{
unsigned char toc[255];memset(toc,0,255);
const unsigned char frames[48]={0};memset((void *)&frames,0,48);
short size[48];memset(size,0,48*sizeof(short));
int payloadoffset=0;
//The object OpusLib is dynamically loaded with the functions exported from libopus*.dll
int nof=OpusLib.opus_packet_parse(seg,soh->SegmentTable[i],toc,(const unsigned char * *)&frames,size,&payloadoffset);
DecodeOggSegment(seg,soh->SegmentTable[i]);
}
       int res;
       if(soh->SegmentTable[i]>8)
       {
               SomethingToDoWithOpus *stdwo=(SomethingToDoWithOpus *)seg;
               if(
                       stdwo->h[0]=='O' &&
                       stdwo->h[1]=='p' &&
                       stdwo->h[2]=='u' &&
                       stdwo->h[3]=='s'
               )
               {
                       if(
                               stdwo->h[4]=='H' &&
                               stdwo->h[5]=='e' &&
                               stdwo->h[6]=='a' &&
                               stdwo->h[7]=='d'
                       )
                       {
                               if(URLOpusTags)
                               {
                                       //The object Opus is a struct that is dynamically loaded with the functions exported by libopusfile*.dll
Opus.opus_tags_clear(URLOpusTags);
                                       delete URLOpusTags;URLOpusTags=NULL;
                               }
                               if(URLOpusHead)
                               {
                                       delete URLOpusHead;URLOpusHead=NULL;
                               }
                               res=Opus.opus_head_parse(&oh,seg,soh->SegmentTable[i]);
                               if(res==0)
                               {
                                       URLOpusHead=new OpusHead;
                                       memcpy(URLOpusHead,&oh,sizeof(oh));
                                       if(CurPPL)
                                       {
                                               CurPPL->Out->Close();
                                               CurPPL->Out->Open(48000,URLOpusHead->channel_count,16,0,0);
                                       }
                                       int error=0;
                                       URLOpusDecoder=OpusLib.opus_decoder_create(48000,URLOpusHead->channel_count,&error);
                                       if(error)
                                       {
                                               ErrorStop();
                                               return;
                                       }
                               }
                       }
                       else
                       if(
                               stdwo->h[4]=='T' &&
                               stdwo->h[5]=='a' &&
                               stdwo->h[6]=='g' &&
                               stdwo->h[7]=='s'
                       )
                       {
                               if(URLOpusTags)
                               {
                                       Opus.opus_tags_clear(URLOpusTags);
                                       delete URLOpusTags;URLOpusTags=NULL;
                               }
                               URLOpusTags=new OpusTags;memset(URLOpusTags,0,sizeof(OpusTags));
                               Opus.opus_tags_init(URLOpusTags);
                               res=Opus.opus_tags_parse(URLOpusTags,seg,soh->SegmentTable[i]);
                               if(res)
                               {
                                       Opus.opus_tags_clear(URLOpusTags);
                                       delete URLOpusTags;URLOpusTags=NULL;
                               }
                       }
               }
       }
       ss+=soh->SegmentTable[i];
}
}
//---------------------------------------------------------------------------
void __fastcall TPlaybackThread::DecodeOggSegment(char * buff, int len)
{
if(URLOpusDecoder)
{
//need 240ms worth of samples at 48khz per channel (x2 for DSPs)
int numofframes=11520;//48000 * 240/1000
int numofsamples=numofframes*URLOpusHead->channel_count;
int len=numofsamples*sizeof(opus_int16);
len*=2;//Extra padding for DSPs
short *nbuffers=(short *)new unsigned char[len];
len/=2;
int sr=OpusLib.opus_decode(URLOpusDecoder,buff,len,nbuffers,numofframes,0);
//At this point, opus_decode is only returning -4 for everything.
if(sr>0)
{
if(CurPPL)
{
CurPPL->Out->Write((char *)nbuffers,sr*sizeof(opus_int16));
}
}
       else if(sr==0)
       {
               //Do nothing
       }
else
{
ErrorStop();
}
delete [] nbuffers;
}
}
//---------------------------------------------------------------------------
  • Last Edit: 15 November, 2012, 10:40:47 AM by thinktink

  • thinktink
  • [*]
Opus inside OggPages
Reply #3
I am not familiar at all with any of the internals/packets/pages/whatevers of live streams.  I can get my plugin to connect to a server, get the data it's outchucking, parse out the HTTP header, parse the seperate Ogg Pages, get the OpusHead, and get the OpusTags, but that's it.  I tried sending, just the Ogg pages, a chain of Ogg pages, individual segments in the pages, to all of the in-memory functions of the libraries but they all either return error code -133 (OP_EBADHEADER), -137 (OP_EBADLINK), or -139 (OP_EBADTIMESTAMP).

Live streams are exactly like files. You shouldn't need to do any parsing, you should be able to pass the data to opusfile using the callback apis.

Opusfile has good http support internally, but unfortunately its POSIX-api only at this point so it doesn't include that functionality on Windows. I think it would be great if someone wanted to work on that.

Quote
Can somebody tell me how to decode an Ogg Opus stream with those libraries or if it's not possible to?

Can you post your code?  It's easier to point out problems in existing code then to walk over the whole process and hopefully clarify something for you by accident.

Hangon, I found a major issue in my code.  Must be a blonde moment.  I created a variable with the same name as the incoming function parameter name, "len" and passing it instead of the actual size of the segment into the decode function.  I changed it to "outlen" and now it's giving me samples, but not all the time.  It sounds sped up sometimes with different servers and it constantly cuts out after the initial incoming packet frame burst from the server.

Opus inside OggPages
Reply #4
Hi

You can use easy edcast to feed opus stream.
i use it too. works.

http://www.stream24.com/tune-in/r1199-4.pls

  • Dynamic
  • [*][*][*][*][*]
Opus inside OggPages
Reply #5
You can use easy edcast to feed opus stream.
i use it too. works.

http://www.stream24.com/tune-in/r1199-4.pls


Your linked stream says Vorbis in fb2k, not Opus.
Dynamic – the artist formerly known as DickD

  • thinktink
  • [*]
Opus inside OggPages
Reply #6
I've made significant progress but I'm still having problems.  I'm parsing the OggS pages myself to retrieve the stream header and metadata and am sending all of the OggS segments (except the ones containing OpusHead and OpusTags) from those pages directly to the "opus_decode" function which sometimes returns the number of samples and sometimes returns -4 (OPUS_INVALID_PACKET).  And when the function does return with a sample count some of the audio sounds distorted or it introduces junk audio data.

I'm testing it with actual radio station servers (which aren't anywhere near as bad sounding but still introduces bad audio data here-and-there) and a file from a local webserver (that sounds absolutely fine when played back locally with the opuslibfile*.dll functions.

What am I doing wrong?  Am I supposed to treat the segments differently in certain situations?  Do I need to combine some?  If I do, how do I figure out when and how?

tia

  • nu774
  • [*][*][*][*][*]
  • Developer
Opus inside OggPages
Reply #7
Made a (quick & dirty) patch for opusfile, which opens http support on win32:
[ Specified attachment is not available ]
This patch is built against the latest commit of the official git repository:
Code: [Select]
commit f2e27e4d1aa6e1b12b1af5bce166d1c3b10473dd
Author: Ralph Giles <giles@mozilla.com>
Date:   Wed Nov 14 11:05:32 2012 -0800
I used mingw-w64 toolchain on Linux.

Some tweak might be still needed to take care of OPENSSL_AppLink to get https support working.
In win32, user application of openssl is required to do
Code: [Select]
#include <openssl/applink.c>
or something, when openssl is compiled with OPENSSL_USE_APPLINK.
I don't know how it should be taken care of, from the library point of view (it must be done by user of libopusfile, since openssl always searches that function in executable module).

  • thinktink
  • [*]
Opus inside OggPages
Reply #8
Made a (quick & dirty) patch for opusfile, which opens http support on win32:
(Attachment Link)
This patch is built against the latest commit of the official git repository:
Code: [Select]
commit f2e27e4d1aa6e1b12b1af5bce166d1c3b10473dd
Author: Ralph Giles <giles@mozilla.com>
Date:   Wed Nov 14 11:05:32 2012 -0800
I used mingw-w64 toolchain on Linux.

Some tweak might be still needed to take care of OPENSSL_AppLink to get https support working.
In win32, user application of openssl is required to do
Code: [Select]
#include <openssl/applink.c>
or something, when openssl is compiled with OPENSSL_USE_APPLINK.
I don't know how it should be taken care of, from the library point of view (it must be done by user of libopusfile, since openssl always searches that function in executable module).

Well rats, that didn't work.
Code: [Select]
C:\Program Files\GnuWin32\bin>patch < winsock.patch
can't find file to patch at input line 5
Perhaps you should have used the -p or --strip option?
The text leading up to this was:
--------------------------
|diff --git a/Makefile.am b/Makefile.am
|index e572ed8..371d61e 100644
|--- a/Makefile.am
|+++ b/Makefile.am
--------------------------
File to patch: libopusfile-0.dll
patching file libopusfile-0.dll
Hunk #1 FAILED at 12.
1 out of 1 hunk FAILED -- saving rejects to file libopusfile-0.dll.rej
can't find file to patch at input line 23
Perhaps you should have used the -p or --strip option?
The text leading up to this was:
--------------------------
|gure.ac b/configure.ac
|index 3133b7f..fe74c3c 100644
|--- a/configure.ac
|+++ b/configure.ac
--------------------------
File to patch:


Just as a side note, I tried using the libogg functions to parse the packets out of the stream instead of finding and parsing them myself.  It made it worse!  lol  When I sent the packets to the decode function sometimes the decode function would throw floating point errors and all the audio data it did return (when it didn't throw exceptions) sounded like uninitialized memory.

  • nu774
  • [*][*][*][*][*]
  • Developer
Opus inside OggPages
Reply #9
This patch is to be applied to the source code, not to the libopusfile binary.

  • saratoga
  • [*][*][*][*][*]
Opus inside OggPages
Reply #10
Well rats, that didn't work.


Just so you understand, a patch is a text file containing source code that you can combine with other source code and then compile into a binary.  Thats why nu774 suggested using the minigw c compiler.

  • thinktink
  • [*]
Opus inside OggPages
Reply #11
This patch is to be applied to the source code, not to the libopusfile binary.

I didn't compile the libraries, I just extracted the DLLs from the archive.

The codec library archive I downloaded didn't come with the source code.  Where can get the appropriate source to download and will the patch for the source work on a 32bit compile?

  • lvqcl
  • [*][*][*][*][*]
  • Developer
Opus inside OggPages
Reply #12

  • thinktink
  • [*]
Opus inside OggPages
Reply #13

  • NullC
  • [*][*][*]
  • Developer
Opus inside OggPages
Reply #14
Just as a side note, I tried using the libogg functions to parse the packets out of the stream instead of finding and parsing them myself.  It made it worse!  lol  When I sent the packets to the decode function sometimes the decode function would throw floating point errors and all the audio data it did return (when it didn't throw exceptions) sounded like uninitialized memory.


Then you're using them wrong— but I can't tell how because you haven't described what you're doing.  This has worked for many other people without issue for Opus and for tens of thousands of developers for other codecs.

You really need to stop trying to parse ogg yourself unless you're willing to sit down, read the docs, read the Ogg RFC, etc.  It isn't complicated, but your guesses are guiding you incorrectly: Segments are not packets.  You need to pass full packets to libopus, which means that the segments must be reassembled.  Libogg does this for the caller.

  • thinktink
  • [*]
Opus inside OggPages
Reply #15
...SNIP...
You really need to stop trying to parse ogg yourself unless you're willing to sit down, read the docs, read the Ogg RFC, etc.  It isn't complicated, but your guesses are guiding you incorrectly: Segments are not packets.  You need to pass full packets to libopus, which means that the segments must be reassembled.  Libogg does this for the caller.

Found the Ogg RFC and found the key piece of information that I was missing.  I managed to do it half right by arbitrarily considering any segment that was 255 bytes long as incomplete, which was half correct.  The other half was segments that are zero bytes, which I didn't realize was legal, to indicate the end of a packet that was exactly modulus(len,255)==0 is actually complete.

  • Arkadas
  • [*]
Opus inside OggPages
Reply #16
Hi

You can use easy edcast to feed opus stream.
i use it too. works.

http://www.stream24.com/tune-in/r1199-4.pls



Please can you give me directions how to stream in opus format by edcast to feed a stream?

Kind regards,

Arkadas