Skip to content

Commit

Permalink
Version 0.5.0
Browse files Browse the repository at this point in the history
Support for AMR NB and AMR WB octet-aligned and bandwidth-efficient
modes
  • Loading branch information
hdiniz committed Aug 23, 2016
1 parent ed18d87 commit 26b61df
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 128 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ _testmain.go
*.exe
*.test
*.prof
!/dist/*/*.exe
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
# rtpdump
Extract audio file from RTP streams in pcap format

Extract media files from RTP streams in pcap format

## codec support

This program is intended to support usual audio/video codecs used on IMS networks (VoLTE/VoWiFi).
Therefore, some codecs might be limited to usual scenarios on these networks.

+ AMR - [RFC 4867](https://tools.ietf.org/html/rfc4867)
Supports bandwidth-efficient and octet-aligned modes.
Single-channel, single-frame per packet only.
+ EVS - [3GPP TS 26.445](http://www.3gpp.org/DynaReport/26445.htm)
*Not yet supported.*
+ H263 - [RFC 2190](https://tools.ietf.org/html/rfc2190)
*Not yet supported.*
+ H264 - [RFC 6184](https://tools.ietf.org/html/rfc6184)
*Not yet supported.*

## usage

+ rtpdump streams [pcap]
displays RTP streams
+ rtpdump dump -i [pcap]
dumps a media stream.
`-i` options is for interactive dump. Codecs and modes are choosen via prompt.
**Currently only mode available**

## compiling

Checkout [gopacket](https://github.com/google/gopacket).
Linux should be straightforward.
For Windows, make sure mingw(32/64) toolchain is on PATH for gopacket WinPcap dependency. Install WinPcap on standard location `C:\WpdPack`

## planned features

1. Support for H264
2. Include stream analisys, packets lost, jitter, etc
3. Media player directly from pcap. ffmpeg support.
4. Jitter buffer to simulate original condition, i.e. packet loss due to jitter

## contributions

Are always appreciated.
124 changes: 52 additions & 72 deletions codecs/amr.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package codecs
import (
"errors"
log "github.com/Sirupsen/logrus"
"github.com/hdiniz/rtpdump/log"
"github.com/hdiniz/rtpdump/rtp"
)

const AMR_NB_MAGIC string = "#!AMR\n"
Expand All @@ -13,18 +14,17 @@ const AMR_WB_SAMPLE_RATE = 16000

type Amr struct {
started bool
configured bool
sampleRate int
octetAligned bool
timestamp uint32
}

func NewAmr() Codec {
return &Amr{}
return &Amr{started: false, configured: false, timestamp: 0}
}

func (amr *Amr) Init() {
amr.started = false
amr.timestamp = 0
}

func (amr *Amr) isWideBand() bool {
Expand Down Expand Up @@ -67,67 +67,74 @@ func (amr *Amr) SetOptions(options map[string]string) error {
} else {
return errors.New("invalid codec option value")
}

amr.configured = true
return nil
}

func (amr *Amr) HandleRtpPacket(timestamp uint32, payload []byte) ([]byte, error) {
func (amr *Amr) HandleRtpPacket(packet *rtp.RtpPacket) (result []byte, err error) {
if !amr.configured {
return nil, amr.invalidState()
}

result = append(result, amr.handleMissingSamples(packet.Timestamp)...)

var speechFrame []byte
if amr.octetAligned {
return amr.handleOaMode(timestamp, payload)
speechFrame, err = amr.handleOaMode(packet.Timestamp, packet.Payload)
} else {
return amr.handleBeMode(timestamp, payload)
speechFrame, err = amr.handleBeMode(packet.Timestamp, packet.Payload)
}
}

func (amr *Amr) handleOaMode(timestamp uint32, payload []byte) ([]byte, error) {

var result []byte
var lostSamplesFromPrevious uint32
var currentTimestamp uint32
if err != nil {
return nil, err
}
result = append(result, speechFrame...)
return result, nil
}

func (amr *Amr) handleMissingSamples(timestamp uint32) (result []byte) {
if amr.timestamp != 0 {
lostSamplesFromPrevious = (timestamp - amr.timestamp) / 160 -1
lostSamplesFromPrevious := ((timestamp - amr.timestamp) / (uint32(amr.sampleRate) / 50)) -1
for i := lostSamplesFromPrevious; i > 0; i-- {
result = append(result, 0xFC)
}
}
return result
}

func (amr *Amr) getSpeechFrameByteSize(frameType int) (size int) {
if amr.isWideBand() {
size = AMR_WB_FRAME_SIZE[frameType]
} else {
size = AMR_NB_FRAME_SIZE[frameType]
}
return
}

func (amr *Amr) handleOaMode(timestamp uint32, payload []byte) ([]byte, error) {

var result []byte
var currentTimestamp uint32

frame := 0
rtpFrameHeader := payload[0:]
// payload header := [CMR(4bit)[R(4bit)][ILL(4bit)(opt)][ILP(4bit)(opt)]
// TOC := [F][FT(4bit)][Q][P][P]
// storage := [0][FT(4bit)][Q][0][0]
cmr := (rtpFrameHeader[0] & 0xF0) >> 4
isLastFrame := (rtpFrameHeader[1] & 0x80) & 0x80 == 0x80
isLastFrame := (rtpFrameHeader[1] & 0x80) & 0x80 == 0x00
frameType := (rtpFrameHeader[1] & 0x78) >> 3
quality := (rtpFrameHeader[1] & 0x04) & 0x04 == 0x04

speechFrameHeader := cmr << 4
speechFrameHeader = speechFrameHeader | (rtpFrameHeader[1] & 0x40)
log.Sdebug("octet-aligned, lastFrame:%t, cmr:%d, frameType:%d, quality:%t",
isLastFrame, cmr, frameType, quality)

var speechFrameSize int
if amr.isWideBand() {
speechFrameSize = AMR_WB_FRAME_SIZE[frameType]
} else {
speechFrameSize = AMR_NB_FRAME_SIZE[frameType]
}
speechFrameHeader := frameType << 3
speechFrameHeader = speechFrameHeader | (rtpFrameHeader[1] & 0x04)

speechFrameSize := amr.getSpeechFrameByteSize(int(frameType))

currentTimestamp = timestamp + uint32(160*frame)
log.WithFields(log.Fields{
"sample-rate": amr.sampleRate,
"rtpFrameHeader": rtpFrameHeader,
"timestamp": timestamp,
"currentTimestamp": currentTimestamp,
"previousTimestamp": amr.timestamp,
"frame": frame,
"octet-aligned": amr.octetAligned,
"cmr": cmr,
"isLastFrame": isLastFrame,
"frameType": frameType,
"quality": quality,
"speechFrameSize": speechFrameSize,
"lostSamplesFromPrevious": lostSamplesFromPrevious,
}).Debug("amr frame")
currentTimestamp = timestamp + uint32((amr.sampleRate / 50)*frame)

if !isLastFrame {
log.Warn("Amr does not suport more than one frame per payload - discarted")
Expand All @@ -146,17 +153,8 @@ func (amr *Amr) handleOaMode(timestamp uint32, payload []byte) ([]byte, error) {

func (amr *Amr) handleBeMode(timestamp uint32, payload []byte) ([]byte, error) {
var result []byte
var lostSamplesFromPrevious uint32
var currentTimestamp uint32


if amr.timestamp != 0 {
lostSamplesFromPrevious = (timestamp - amr.timestamp) / 160 -1
for i := lostSamplesFromPrevious; i > 0; i-- {
result = append(result, 0xFC)
}
}

frame := 0
rtpFrameHeader := payload[0:]
// packing frame with TOC: frame type and quality bit
Expand All @@ -166,32 +164,15 @@ func (amr *Amr) handleBeMode(timestamp uint32, payload []byte) ([]byte, error) {
frameType := (rtpFrameHeader[0] & 0x07) << 1 | (rtpFrameHeader[1] & 0x80) >> 7
quality := (rtpFrameHeader[1] & 0x04) >> 2 & 0x01 == 0x01

log.Sdebug("bandwidth-efficient, lastFrame:%t, cmr:%d, frameType:%d, quality:%t",
isLastFrame, cmr, frameType, quality)

speechFrameHeader := (rtpFrameHeader[0] & 0x07)<<4 | (rtpFrameHeader[1] & 0x80)>>4
speechFrameHeader = speechFrameHeader | (rtpFrameHeader[1] & 0x40)>>4

var speechFrameSize int
if amr.isWideBand() {
speechFrameSize = AMR_WB_FRAME_SIZE[frameType]
} else {
speechFrameSize = AMR_NB_FRAME_SIZE[frameType]
}
speechFrameSize := amr.getSpeechFrameByteSize(int(frameType))

currentTimestamp = timestamp + uint32(160*frame)
log.WithFields(log.Fields{
"sample-rate": amr.sampleRate,
"rtpFrameHeader": rtpFrameHeader,
"timestamp": timestamp,
"currentTimestamp": currentTimestamp,
"previousTimestamp": amr.timestamp,
"frame": frame,
"octet-aligned": amr.octetAligned,
"cmr": cmr,
"isLastFrame": isLastFrame,
"frameType": frameType,
"quality": quality,
"speechFrameSize": speechFrameSize,
"lostSamplesFromPrevious": lostSamplesFromPrevious,
}).Debug("amrnb frame")
currentTimestamp = timestamp + uint32((amr.sampleRate / 50)*frame)

if !isLastFrame {
log.Warn("Amr does not suport more than one frame per payload - discarted")
Expand All @@ -216,7 +197,6 @@ func (amr *Amr) handleBeMode(timestamp uint32, payload []byte) ([]byte, error) {
return result, nil
}


var AmrMetadata = CodecMetadata{
Name: "amr",
LongName: "Adaptative Multi Rate",
Expand Down
3 changes: 2 additions & 1 deletion codecs/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package codecs

import (
"fmt"
"github.com/hdiniz/rtpdump/rtp"
)

type Codec interface {
Init()
SetOptions(options map[string]string) error
HandleRtpPacket(timestamp uint32, payload []byte) ([]byte, error)
HandleRtpPacket(packet *rtp.RtpPacket) ([]byte, error)
GetFormatMagic() []byte
}

Expand Down
22 changes: 22 additions & 0 deletions console/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,25 @@ func Prompt(prompt string) PrintFunction {
return nil
}
}

func ListPrompt(title string, items ...string) PrintFunction {
fmt.Println(len(items))
return func (attempts int) error {
fmt.Println(title)
for i := 0; i < len(items); i++ {
fmt.Printf("(%03d) %s\n", i+1, items[i])
}
fmt.Printf("[%d-%d]: ", 1, len(items))
return nil
}
}

func KeyValuePrompt(title string, keys []string, values []string) PrintFunction {
return func (attempts int) error {
fmt.Println(title)
for i := 0; i < len(keys); i++ {
fmt.Printf("(%s) %s\n", keys[i], values[i])
}
return nil
}
}
Binary file added dist/x32/rtpdump_x32_0.5.0.exe
Binary file not shown.
Binary file added dist/x64/rtpdump_x64_0.5.0.exe
Binary file not shown.
62 changes: 62 additions & 0 deletions log/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package log

import (
"fmt"
)

var TRACE int = 4
var DEBUG int = 3
var INFO int = 2
var WARN int = 1
var ERROR int = 0

var logLevel int = INFO


func SetLevel(level int) {
logLevel = level
}

func slog(level int, msg string, args ...interface{}) {
if logLevel >= level {
fmt.Printf(msg + "\n", args...)
}
}

func log(level int, msg string) {
if logLevel >= level {
fmt.Println(msg)
}
}

func Strace(msg string, args ...interface{}) {
slog(TRACE, msg, args...)
}
func Sdebug(msg string, args ...interface{}) {
slog(DEBUG, msg, args...)
}
func Sinfo(msg string, args ...interface{}) {
slog(INFO, msg, args...)
}
func Swarn(msg string, args ...interface{}) {
slog(WARN, msg, args...)
}
func Serror(msg string, args ...interface{}) {
slog(ERROR, msg, args...)
}

func Trace(msg string) {
log(TRACE, msg)
}
func Debug(msg string) {
log(DEBUG, msg)
}
func Info(msg string) {
log(INFO, msg)
}
func Warn(msg string) {
log(WARN, msg)
}
func Error(msg string) {
log(ERROR, msg)
}
Loading

0 comments on commit 26b61df

Please sign in to comment.