Skip to content

Instantly share code, notes, and snippets.

@maxymania
Last active January 23, 2019 05:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxymania/ff05177458992d5819a3f64b720f7ae1 to your computer and use it in GitHub Desktop.
Save maxymania/ff05177458992d5819a3f64b720f7ae1 to your computer and use it in GitHub Desktop.
Ogg Stream segmenter for netty (4.1.32)
/*
Copyright (c) 2019 Simon Schmidt
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.util.ReferenceCountUtil;
import java.util.List;
/**
*
* @author simon
*/
public class OggDecoder extends ReplayingDecoder<OggDecoder.State> {
public enum State {
ERROR,
OK,
SO,
SggS0,
HDR,
SEGTABLE,
BODY_PREPARE,
BODY,
}
public OggDecoder() {
super(State.OK);
}
private OggPage message;
private static final byte[] MZ = {
(byte)'g',
(byte)'g',
(byte)'S',
0,
};
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Discard on error!.
if(state()==State.ERROR){
ReferenceCountUtil.release(msg);
return;
}
super.channelRead(ctx, msg);
}
@Override
protected void decode(
ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out
) throws Exception {
start:
for(;;)
switch(state()){
case ERROR:
for(;;){
in.readByte();
checkpoint();
}
case OK:
message = new OggPage();
state(State.SO);
case SO:
int before = in.bytesBefore((byte)'O');
if(before>0)
in.skipBytes(before);
in.readByte();
checkpoint(State.SggS0);
case SggS0:
for(byte b:MZ){
byte r = in.readByte();
if(r==b)continue;
checkpoint();
if(r!='O') state(State.SO);
continue start;
}
checkpoint(State.HDR);
case HDR:
message.decodeHeaderNoMZ(in);
checkpoint(State.SEGTABLE);
case SEGTABLE:
message.decodeSegTable(in);
checkpoint(State.BODY);
case BODY:
message.readBody(in);
out.add(message);
checkpoint(State.OK);
message = null;
}
}
}
/*
Copyright (c) 2019 Simon Schmidt
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
/**
*
* @author simon
*/
public class OggPage implements ReferenceCounted{
public static int OGGS_LE = 0x5367674f;
public int oggs,version,bitflags;
public long absoluteGranulatePosition;
public int serialNumber,pageNo,crc;
public int pageSegments;
public int[] segTable;
public ByteBuf body;
public void decodeHeader(ByteBuf buf){
oggs = buf.readIntLE();
version = buf.readUnsignedByte();
decodeHeaderInternal(buf);
}
public void decodeHeaderNoMZ(ByteBuf buf){
oggs = OGGS_LE;
version = 0;
decodeHeaderInternal(buf);
}
private void decodeHeaderInternal(ByteBuf buf){
bitflags = buf.readUnsignedByte();
absoluteGranulatePosition = buf.readLongLE();
serialNumber = buf.readIntLE();
pageNo = buf.readIntLE();
crc = buf.readIntLE();
pageSegments = buf.readUnsignedByte();
segTable = new int[pageSegments];
}
public boolean validateHeader(){
return
oggs==OGGS_LE &&
version==0;
}
public void decodeSegTable(ByteBuf buf){
for(int i=0;i<pageSegments;++i)
segTable[i] = buf.readUnsignedByte();
}
public boolean validateSegmentTable(){
/*
for(int segment:segTable)
if(segment==0)
return false;
// */
return true;
}
public void readBody(ByteBuf buf){
int length = 0;
for(int segment:segTable)
length += segment;
body = buf.readBytes(length);
}
public void encode(ByteBuf buf){
buf.writeIntLE(oggs);
buf.writeByte(version);
buf.writeByte(bitflags);
buf.writeLongLE(absoluteGranulatePosition);
buf.writeIntLE(serialNumber);
buf.writeIntLE(pageNo);
buf.writeIntLE(crc);
buf.writeByte(segTable.length);
for(int seg:segTable)
buf.writeByte(seg);
int length = 0;
for(int segment:segTable)
length += segment;
buf.writeBytes(body, body.readerIndex(), length);
}
public int hintSize(){
return 27+segTable.length+body.readableBytes();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("OggPage");
sb.append("{");
sb.append(";validHeader=").append(validateHeader());
sb.append(";validateSegmentTable=").append(validateSegmentTable());
sb.append(";version=").append(version);
sb.append(";agp=").append(absoluteGranulatePosition);
sb.append(";serialNumber=").append(serialNumber);
sb.append(";pageNo=").append(pageNo);
sb.append("}");
return sb.toString();
}
@Override
public int refCnt() {
return ReferenceCountUtil.refCnt(body);
}
@Override
public OggPage retain() {
ReferenceCountUtil.retain(body);
return this;
}
@Override
public OggPage retain(int increment) {
ReferenceCountUtil.retain(body,increment);
return this;
}
@Override
public OggPage touch() {
ReferenceCountUtil.touch(body);
return this;
}
@Override
public OggPage touch(Object hint) {
ReferenceCountUtil.touch(body,hint);
return this;
}
@Override
public boolean release() {
return ReferenceCountUtil.release(body);
}
@Override
public boolean release(int decrement) {
return ReferenceCountUtil.release(body,decrement);
}
}
/*
Copyright (c) 2019 Simon Schmidt
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
/**
* <a href="https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2">here</a> and
* <a href="https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-820005">here</a>
* @author Simon Schmidt
*/
public class VorbisHeader {
/**
* 1 = identification header<br>
* 3 = comment header
* .
*/
public int type;
/**
* Should be "vorbis".
*/
public String vorbis;
/**
* type = 1, identification header.
*/
public int
vorbis_version,
audio_channels,
audio_sample_rate,
bitrate_maximum,
bitrate_nominal,
bitrate_minimum,
blocksize_0_and_1;
/**
* type = 3, comment header.
*/
public String vendor,comments[];
public void parse(OggPage page){
ByteBuf body = page.body;
body.markReaderIndex();
try{
type = body.readUnsignedByte();
vorbis = body.readSlice(6).toString(StandardCharsets.ISO_8859_1);
switch(type){
case 1:
vorbis_version = body.readIntLE();
audio_channels = body.readUnsignedByte();
audio_sample_rate = body.readIntLE();
bitrate_maximum = body.readIntLE();
bitrate_nominal = body.readIntLE();
bitrate_minimum = body.readIntLE();
blocksize_0_and_1 = body.readUnsignedByte();
break;
case 3:
vendor = body.readSlice(body.readIntLE()).toString(StandardCharsets.UTF_8);
int n = body.readIntLE();
comments = new String[n];
for(int i=0;i<n;++i)
comments[i] = body.readSlice(body.readIntLE()).toString(StandardCharsets.UTF_8);
}
}finally{
body.resetReaderIndex();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("VorbisHeader");
sb.append("{");
switch(type){
case 1:
sb.append("[").append(vorbis).append("]");
sb.append("\n[vorbis_version]").append(vorbis_version);
sb.append("\n[audio_channels]").append(audio_channels);
sb.append("\n[audio_sample_rate]").append(audio_sample_rate);
sb.append("\n[bitrate_maximum]").append(bitrate_maximum);
sb.append("\n[bitrate_nominal]").append(bitrate_nominal);
sb.append("\n[bitrate_minimum]").append(bitrate_minimum);
sb.append("\n[blocksize_0_and_1]").append(blocksize_0_and_1);
break;
case 3:
sb.append("[").append(vorbis).append("]");
sb.append("\n[vendor]").append(vendor);
for(String comment:comments)
sb.append("\n[comment]").append(comment);
default:
sb.append("type=").append(type).append("[").append(vorbis).append("]");
}
sb.append("}");
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment