<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">Hi,<br class=""><br class="">I seem to have found a bug with seeking in opusfile in some situations, though this might be better directed to the ogg list. I’ve included a short description below and a test case C program that describes it more detail (comments) and reproduces the issue when used with a file which contains packets that have been split over a page boundary (less&nbsp;common than I thought). The test program is not the cleanest code as I chopped up a much longer C++ test program I wrote to only include the relevant parts to reproduce the bug and converted it to C89 as I thought this would be preferred. I would try and fix it and submit a patch myself, but this is all on the companies time and I’ve got managers&nbsp;pressuring me to get on with fixing other things, sorry.<br class=""><br class="">Short Description:<br class="">When a file contains ogg packets which have been split over ogg page boundaries, using op_pcm_seek to seek to a position that falls into the packet that has been split results in an OP_EBADLINK error being returned. This seems to be because the library doesn’t account for the fact that packets that do not finish on the page, do not count towards its&nbsp;granule position, even if they start on it. The seek function notices that the target position is greater than the granule position of the page that the packet starts on and seeks to the next page. It then re-calculates the previous packets GP by subtracting the new page duration from the new page GP which gives it the GP of the packet after the one which&nbsp;has been split. Seeing that it s now past the target position, it returns an OP_EBADLINK error.<br class=""><br class=""><br class="">To reproduce with the test program, you need a file which definitely has packets split over page boundaries. I can provide an opus file which has split packets if that is easier for you. I wrote this in VS, so sorry in clang or GCC moan about something, I wasn’t sure if it was a good idea to attach a file so I’ve just pasted it in below.<div class=""><br class=""></div><div class=""><br class=""></div><div class="">#include&nbsp;&lt;stdio.h&gt;<br class="">#include&nbsp;&lt;ogg/ogg.h&gt;<br class="">#include&nbsp;&lt;opus.h&gt;<br class="">#include&nbsp;&lt;opusfile.h&gt;<br class="">#include&nbsp;&lt;stdint.h&gt;<br class=""><br class="">int64_t&nbsp;FindBrokenSeekPoint(char*&nbsp;filename)<br class="">{<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// Visual studio doesn't do C99 and I'm converting from C++<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// so dump all the decelerations here to make it build as C89.<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>static&nbsp;const&nbsp;long&nbsp;BufferSize&nbsp;=&nbsp;8192;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_sync_state&nbsp;m_syncState;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_stream_state&nbsp;m_streamState;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int64_t&nbsp;m_lastGranulePos&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int64_t&nbsp;failingSeekPoint&nbsp;=&nbsp;-1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int32_t&nbsp;serialno&nbsp;=&nbsp;-1;<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;fpp,&nbsp;spf;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;decoding&nbsp;=&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_page&nbsp;page;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>char*&nbsp;buffer&nbsp;=&nbsp;NULL;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>long&nbsp;bytesRead&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>uint8_t&nbsp;headerFLags&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;pageContinuesPacket&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;firstPacketInPage&nbsp;=&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;packetsToFetch&nbsp;=&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int&nbsp;packetOutRet;<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>FILE*&nbsp;fp&nbsp;=&nbsp;fopen(filename,&nbsp;"rb");<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_sync_init(&amp;m_syncState);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>while(decoding)<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>while(ogg_sync_pageout(&amp;m_syncState,&nbsp;&amp;page)&nbsp;!=&nbsp;1)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>buffer&nbsp;=&nbsp;ogg_sync_buffer(&amp;m_syncState,&nbsp;BufferSize);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>bytesRead&nbsp;=&nbsp;fread(buffer,&nbsp;sizeof(char),&nbsp;BufferSize,&nbsp;fp);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>ogg_sync_wrote(&amp;m_syncState,&nbsp;bytesRead);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// If this is the last page, then don't loop again.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>if(ogg_page_eos(&amp;page)&nbsp;&gt;&nbsp;0)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>decoding&nbsp;=&nbsp;0;<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>if(serialno&nbsp;&lt;&nbsp;0)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>serialno&nbsp;=&nbsp;ogg_page_serialno(&amp;page);;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>ogg_stream_init(&amp;m_streamState,&nbsp;serialno);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>headerFLags&nbsp;=&nbsp;page.header[5];<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>pageContinuesPacket&nbsp;=&nbsp;headerFLags&nbsp;&amp;&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>ogg_stream_pagein(&amp;m_streamState,&nbsp;&amp;page);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>firstPacketInPage&nbsp;=&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>packetsToFetch&nbsp;=&nbsp;1;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>while(packetsToFetch)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>ogg_packet&nbsp;packet;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>packetOutRet&nbsp;=&nbsp;ogg_stream_packetout(&amp;m_streamState,&nbsp;&amp;packet);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// This is the test case. If you try and seek, using op_pcm_seek,<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// to a position, that when adjusted by 80ms for the discarded data<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// lies within the first half of a packet that has been split over&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// an ogg page boundary, op_pcm_seek will return OP_EBADLINK. This&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// seems to be because the library doesn't take into account that<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// split packets are not counted towards the granule position of&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// the page they begin on. The library finds the page that the&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// packet beings on and compares the target position to the page&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// granule position and finds that it is larger. It then seeks to&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// the beginning of the next page and is now past the point where<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// the audio we want is and doesn't search backwards ever. It then&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// re-calculates the granule position of the previous packet by&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// subtracting the new page duration from the new page granule&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// position and checks to see if the target position is greater<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// than the last packet granule position, which it now is due to<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>// the re-calculation and thus throws the OP_EBADLINK error.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>if(packetOutRet&nbsp;==&nbsp;1&nbsp;&amp;&amp;&nbsp;firstPacketInPage&nbsp;&amp;&amp;&nbsp;pageContinuesPacket)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// Seek to just after the beginning of the split packet, which<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// should begin at m_lastGranulePos.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>decoding&nbsp;=&nbsp;0;<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// Seeking discards 80ms so the actual returned seek point needs to be 80ms<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// ahead of the failing point to trigger the bug. I'm assuming 20ms frames<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// (960 samples) to make life easy for this test case. So we need to add<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// 960 * 4 to m_lastGranulePos and then a small amount to put it in the<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// the first half of the split packet, 10 should do.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>failingSeekPoint&nbsp;=&nbsp;m_lastGranulePos&nbsp;+&nbsp;(960*4)&nbsp;+&nbsp;10;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>break;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>}<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>else&nbsp;if(packetOutRet&nbsp;==&nbsp;1)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>if(packet.packetno&nbsp;&lt;&nbsp;2)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                        </span>continue;<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// Calculate packet granule positions<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>fpp&nbsp;=&nbsp;opus_packet_get_nb_frames(packet.packet,&nbsp;packet.bytes);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>spf&nbsp;=&nbsp;opus_packet_get_samples_per_frame(packet.packet,&nbsp;48000);<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>spf&nbsp;*=&nbsp;fpp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>m_lastGranulePos&nbsp;+=&nbsp;spf;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>}<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>else<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>// Need more data or unrecoverable error.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                                </span>packetsToFetch&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>}<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>firstPacketInPage&nbsp;=&nbsp;0;<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>fclose(fp);<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_stream_clear(&amp;m_streamState);<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>ogg_sync_clear(&amp;m_syncState);<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>return&nbsp;failingSeekPoint;<br class="">}<br class=""><br class="">int&nbsp;main(int&nbsp;argc,&nbsp;char*&nbsp;argv[])<br class="">{<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>char*&nbsp;filename&nbsp;=&nbsp;"TestCase.opus";<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// This is the test case. If you try and seek, using op_pcm_seek,<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// to a position, that when adjusted by 80ms for the discarded data<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// lies within the first half of a packet that has been split over&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// an ogg page boundary, op_pcm_seek will return OP_EBADLINK. This&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// seems to be because the library doesn't take into account that<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// split packets are not counted towards the granule position of&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// the page they begin on. The library finds the page that the&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// packet beings on and compares the target position to the page&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// granule position and finds that it is larger. It then seeks to&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// the beginning of the next page and is now past the point where<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// the audio we want is and doesn't search backwards ever. It then&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// re-calculates the granule position of the previous packet by&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// subtracting the new page duration from the new page granule&nbsp;<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// position and checks to see if the target position is greater<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// than the last packet granule position, which it now is due to<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// the re-calculation and thus throws the OP_EBADLINK error.<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>int64_t&nbsp;failingSeekPoint&nbsp;=&nbsp;FindBrokenSeekPoint(filename);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>if(failingSeekPoint&nbsp;&gt;=&nbsp;0)<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>OggOpusFile*&nbsp;opfile&nbsp;=&nbsp;op_open_file(filename,&nbsp;NULL);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// Seeking to somewhere in the first packet should return<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// OP_EBADLINK, however seeking discards 80ms so the actual<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// requested point needs to be 80ms ahead of the failing point.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// This is handled by FindBrokenSeekPoint which has already<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// adjusted the seek point to account for the 80ms.<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// Adjust the raw GP to pcm pos by subtracting pre-skip amount<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>// of 48K.<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>int&nbsp;retcode&nbsp;=&nbsp;op_pcm_seek(opfile,&nbsp;failingSeekPoint-4800);<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>if(retcode&nbsp;&lt;&nbsp;0)<br class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>printf("Seek failed with OP_EBADLINK!\n");<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>op_free(opfile);<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>else<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>{<br class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>printf("No packets split over page boundaries in file.");<br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}<br class=""><br class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>return&nbsp;0;<br class="">}</div><br class=""><br class=""><div apple-content-edited="true" class="">
<div class="" style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><div class=""><div style="margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><b class=""><span class="" style="font-size: 9.5pt; font-family: Arial, sans-serif;">Simon Jackson</span></b><span class="" style="font-size: 9.5pt;"><o:p class=""></o:p></span></div></div><div class=""><div style="margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><b class=""><span class="" style="font-size: 9.5pt; font-family: Arial, sans-serif;">Software</span></b><b class=""><span class="" style="font-size: 10pt; font-family: Arial, sans-serif;">&nbsp;</span></b><b class=""><span class="" style="font-size: 9.5pt; font-family: Arial, sans-serif;">Developer</span></b><o:p class=""></o:p></div></div><div class=""><div class=""><div style="margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><i class=""><span class="" style="font-size: 10pt; font-family: Arial, sans-serif;">Sonocent Ltd.</span></i><o:p class=""></o:p></div></div></div><div class=""><div class=""><div style="margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><span class="" style="font-size: 10pt; font-family: Arial, sans-serif;">Tel: 0113 815 0223<span class="" style="color: red;">&nbsp;</span></span><o:p class=""></o:p></div></div></div><div class=""><div class=""><div style="margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><a href="http://www.sonocent.com/" target="_blank" class="" style="color: purple;"><span class="" style="font-size: 10pt; font-family: Arial, sans-serif;">www.sonocent.com</span></a><o:p class=""></o:p></div></div></div><div class=""><div class=""><div style="color: rgb(0, 0, 0); font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0in 0in 0.0001pt; font-size: 12pt; font-family: 'Times New Roman', serif;" class=""><span class="" style="color: purple; font-size: 10pt; font-family: Arial, sans-serif;"><a href="https://twitter.com/AudioNotetaker" target="_blank" class="" style="color: purple;">@AudioNotetaker</a></span></div></div></div></div><span><img height="80" width="241" apple-inline="yes" id="27C3A887-B3F5-4E70-8680-DD4BC14670F1" apple-width="yes" apple-height="yes" src="cid:E5C34AC2-1F02-417A-8F1A-50B0816DBE29@Home" class=""></span>
</div>
<br class=""><div class=""><br class=""></div><div class=""><br class=""></div></body></html>