Change-Id: Icdc97a9dc849bba6ba6add12d0bdd17f8b7712cdchanges/85/9685/8
| @ -0,0 +1 @@ | |||
| ../lib/rtplib.c | |||
| @ -0,0 +1 @@ | |||
| *.o | |||
| @ -0,0 +1,74 @@ | |||
| #include "rtplib.h" | |||
| #include <arpa/inet.h> | |||
| #include "str.h" | |||
| #include "log.h" | |||
| struct rtp_extension { | |||
| u_int16_t undefined; | |||
| u_int16_t length; | |||
| } __attribute__ ((packed)); | |||
| int rtp_payload(struct rtp_header **out, str *p, const str *s) { | |||
| struct rtp_header *rtp; | |||
| struct rtp_extension *ext; | |||
| const char *err; | |||
| err = "short packet (header)"; | |||
| if (s->len < sizeof(*rtp)) | |||
| goto error; | |||
| rtp = (void *) s->s; | |||
| err = "invalid header version"; | |||
| if ((rtp->v_p_x_cc & 0xc0) != 0x80) /* version 2 */ | |||
| goto error; | |||
| if (!p) | |||
| goto done; | |||
| *p = *s; | |||
| /* fixed header */ | |||
| str_shift(p, sizeof(*rtp)); | |||
| /* csrc list */ | |||
| err = "short packet (CSRC list)"; | |||
| if (str_shift(p, (rtp->v_p_x_cc & 0xf) * 4)) | |||
| goto error; | |||
| if ((rtp->v_p_x_cc & 0x10)) { | |||
| /* extension */ | |||
| err = "short packet (extension header)"; | |||
| if (p->len < sizeof(*ext)) | |||
| goto error; | |||
| ext = (void *) p->s; | |||
| err = "short packet (header extensions)"; | |||
| if (str_shift(p, 4 + ntohs(ext->length) * 4)) | |||
| goto error; | |||
| } | |||
| done: | |||
| *out = rtp; | |||
| return 0; | |||
| error: | |||
| ilog(LOG_WARNING | LOG_FLAG_LIMIT, "Error parsing RTP header: %s", err); | |||
| return -1; | |||
| } | |||
| int rtp_padding(struct rtp_header *header, str *payload) { | |||
| if (!(header->v_p_x_cc & 0x20)) | |||
| return 0; // no padding | |||
| if (payload->len == 0) | |||
| return -1; | |||
| unsigned int padding = (unsigned char) payload->s[payload->len - 1]; | |||
| if (payload->len < padding) | |||
| return -1; | |||
| payload->len -= padding; | |||
| return 0; | |||
| } | |||
| @ -0,0 +1,22 @@ | |||
| #ifndef _RTPLIB_H_ | |||
| #define _RTPLIB_H_ | |||
| #include <stdint.h> | |||
| #include "str.h" | |||
| struct rtp_header { | |||
| unsigned char v_p_x_cc; | |||
| unsigned char m_pt; | |||
| uint16_t seq_num; | |||
| uint32_t timestamp; | |||
| uint32_t ssrc; | |||
| uint32_t csrc[]; | |||
| } __attribute__ ((packed)); | |||
| int rtp_payload(struct rtp_header **out, str *p, const str *s); | |||
| int rtp_padding(struct rtp_header *header, str *payload); | |||
| #endif | |||
| @ -0,0 +1,200 @@ | |||
| #include "decoder.h" | |||
| #include <libavcodec/avcodec.h> | |||
| #include <libavformat/avformat.h> | |||
| #include <glib.h> | |||
| #include <stdint.h> | |||
| #include "types.h" | |||
| #include "log.h" | |||
| struct decoder_s { | |||
| AVCodecContext *avcctx; | |||
| AVPacket avpkt; | |||
| AVFrame *frame; | |||
| unsigned long rtp_ts; | |||
| uint64_t pts; | |||
| }; | |||
| struct output_s { | |||
| AVCodecContext *avcctx; | |||
| AVFormatContext *fmtctx; | |||
| AVStream *avst; | |||
| AVPacket avpkt; | |||
| }; | |||
| decoder_t *decoder_new(unsigned int payload_type, const char *payload_str) { | |||
| decoder_t *ret = g_slice_alloc0(sizeof(*ret)); | |||
| // XXX error reporting | |||
| AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_PCM_ALAW); | |||
| ret->avcctx = avcodec_alloc_context3(codec); | |||
| if (!ret->avcctx) | |||
| goto err; | |||
| ret->avcctx->channels = 1; | |||
| ret->avcctx->sample_rate = 8000; | |||
| int i = avcodec_open2(ret->avcctx, codec, NULL); | |||
| if (i) | |||
| goto err; | |||
| av_init_packet(&ret->avpkt); | |||
| ret->frame = av_frame_alloc(); | |||
| if (!ret->frame) | |||
| goto err; | |||
| ret->pts = (uint64_t) -1LL; | |||
| ret->rtp_ts = (unsigned long) -1L; | |||
| return ret; | |||
| err: | |||
| decoder_close(ret); | |||
| return NULL; | |||
| } | |||
| static int output_add(output_t *output, AVFrame *frame) { | |||
| if (!output) | |||
| return -1; | |||
| #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) | |||
| int ret = avcodec_send_frame(output->avcctx, frame); | |||
| dbg("send frame ret %i", ret); | |||
| if (ret) | |||
| return -1; | |||
| ret = avcodec_receive_packet(output->avcctx, &output->avpkt); | |||
| dbg("receive packet ret %i", ret); | |||
| if (ret) | |||
| return -1; | |||
| #else | |||
| int got_packet = 0; | |||
| int ret = avcodec_encode_audio2(output->avcctx, &output->avpkt, frame, &got_packet); | |||
| dbg("encode frame ret %i, got packet %i", ret, got_packet); | |||
| if (!got_packet) | |||
| return 0; | |||
| #endif | |||
| av_write_frame(output->fmtctx, &output->avpkt); | |||
| return 0; | |||
| } | |||
| int decoder_input(decoder_t *dec, const str *data, unsigned long ts, output_t *output) { | |||
| if (G_UNLIKELY(!dec)) | |||
| return -1; | |||
| if (G_UNLIKELY(dec->rtp_ts == (unsigned long) -1L)) { | |||
| // initialize pts | |||
| dec->pts = 0; | |||
| } | |||
| else { | |||
| // shift pts according to rtp ts shift | |||
| dec->pts += (ts - dec->rtp_ts) * output->avst->time_base.num * 8000 / output->avst->time_base.den; | |||
| } | |||
| dec->rtp_ts = ts; | |||
| dec->avpkt.data = (unsigned char *) data->s; | |||
| dec->avpkt.size = data->len; | |||
| dec->avpkt.pts = dec->pts; | |||
| #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) | |||
| int ret = avcodec_send_packet(dec->avcctx, &dec->avpkt); | |||
| dbg("send packet ret %i", ret); | |||
| if (ret) | |||
| return -1; | |||
| ret = avcodec_receive_frame(dec->avcctx, dec->frame); | |||
| dbg("receive frame ret %i", ret); | |||
| if (ret) | |||
| return -1; | |||
| #else | |||
| int got_frame = 0; | |||
| int ret = avcodec_decode_audio4(dec->avcctx, dec->frame, &got_frame, &dec->avpkt); | |||
| dbg("decode frame ret %i, got frame %i", ret, got_frame); | |||
| if (!got_frame) | |||
| return 0; | |||
| #endif | |||
| dec->frame->pts = dec->frame->pkt_pts; | |||
| output_add(output, dec->frame); | |||
| return 0; | |||
| } | |||
| output_t *output_new(const char *filename) { | |||
| output_t *ret = g_slice_alloc0(sizeof(*ret)); | |||
| // XXX error reporting | |||
| ret->fmtctx = avformat_alloc_context(); | |||
| if (!ret->fmtctx) | |||
| goto err; | |||
| ret->fmtctx->oformat = av_guess_format("wav", NULL, NULL); // XXX better way? | |||
| if (!ret->fmtctx->oformat) | |||
| goto err; | |||
| AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); | |||
| // XXX error handling | |||
| ret->avst = avformat_new_stream(ret->fmtctx, codec); | |||
| if (!ret->avst) | |||
| goto err; | |||
| #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) | |||
| ret->avcctx = avcodec_alloc_context3(codec); | |||
| if (!ret->avcctx) | |||
| goto err; | |||
| #else | |||
| ret->avcctx = ret->avst->codec; | |||
| #endif | |||
| ret->avcctx->channels = 1; | |||
| ret->avcctx->sample_rate = 8000; | |||
| ret->avcctx->sample_fmt = AV_SAMPLE_FMT_S16; | |||
| ret->avcctx->time_base = (AVRational){8000,1}; | |||
| ret->avst->time_base = ret->avcctx->time_base; | |||
| #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) | |||
| avcodec_parameters_from_context(ret->avst->codecpar, ret->avcctx); | |||
| #endif | |||
| int i = avcodec_open2(ret->avcctx, codec, NULL); | |||
| if (i) | |||
| goto err; | |||
| i = avio_open(&ret->fmtctx->pb, filename, AVIO_FLAG_WRITE); | |||
| if (i < 0) | |||
| goto err; | |||
| i = avformat_write_header(ret->fmtctx, NULL); | |||
| if (i) | |||
| goto err; | |||
| av_init_packet(&ret->avpkt); | |||
| return ret; | |||
| err: | |||
| output_close(ret); | |||
| return NULL; | |||
| } | |||
| void decoder_close(decoder_t *dec) { | |||
| if (!dec) | |||
| return; | |||
| avcodec_free_context(&dec->avcctx); | |||
| av_frame_free(&dec->frame); | |||
| g_slice_free1(sizeof(*dec), dec); | |||
| } | |||
| void output_close(output_t *output) { | |||
| if (!output) | |||
| return; | |||
| av_write_trailer(output->fmtctx); | |||
| avcodec_close(output->avcctx); | |||
| avio_closep(&output->fmtctx->pb); | |||
| avformat_free_context(output->fmtctx); | |||
| g_slice_free1(sizeof(*output), output); | |||
| } | |||
| @ -0,0 +1,16 @@ | |||
| #ifndef _DECODER_H_ | |||
| #define _DECODER_H_ | |||
| #include "types.h" | |||
| #include "str.h" | |||
| decoder_t *decoder_new(unsigned int payload_type, const char *payload_str); | |||
| int decoder_input(decoder_t *, const str *, unsigned long ts, output_t *); | |||
| void decoder_close(decoder_t *); | |||
| output_t *output_new(const char *filename); | |||
| void output_close(output_t *); | |||
| #endif | |||
| @ -0,0 +1,181 @@ | |||
| #include "packet.h" | |||
| #include <netinet/ip.h> | |||
| #include <netinet/ip6.h> | |||
| #include <netinet/udp.h> | |||
| #include <glib.h> | |||
| #include <unistd.h> | |||
| #include "types.h" | |||
| #include "log.h" | |||
| #include "rtplib.h" | |||
| #include "str.h" | |||
| #include "decoder.h" | |||
| static int packet_cmp(const void *A, const void *B, void *dummy) { | |||
| const packet_t *a = A, *b = B; | |||
| if (a->seq < b->seq) | |||
| return -1; | |||
| if (a->seq > b->seq) | |||
| return 1; | |||
| return 0; | |||
| } | |||
| static void packet_free(void *p) { | |||
| packet_t *packet = p; | |||
| if (!packet) | |||
| return; | |||
| free(packet->buffer); | |||
| g_slice_free1(sizeof(*packet), packet); | |||
| } | |||
| void ssrc_free(void *p) { | |||
| ssrc_t *s = p; | |||
| g_tree_destroy(s->packets); | |||
| output_close(s->output); | |||
| for (int i = 0; i < G_N_ELEMENTS(s->decoders); i++) | |||
| decoder_close(s->decoders[i]); | |||
| g_slice_free1(sizeof(*s), s); | |||
| } | |||
| // mf must be unlocked; returns ssrc locked | |||
| static ssrc_t *ssrc_get(metafile_t *mf, unsigned long ssrc) { | |||
| pthread_mutex_lock(&mf->lock); | |||
| ssrc_t *ret = g_hash_table_lookup(mf->ssrc_hash, GUINT_TO_POINTER(ssrc)); | |||
| if (ret) | |||
| goto out; | |||
| ret = g_slice_alloc0(sizeof(*ret)); | |||
| pthread_mutex_init(&ret->lock, NULL); | |||
| ret->metafile = mf; | |||
| ret->ssrc = ssrc; | |||
| ret->packets = g_tree_new_full(packet_cmp, NULL, NULL, packet_free); | |||
| ret->seq = -1; | |||
| char buf[256]; | |||
| snprintf(buf, sizeof(buf), "%s-%08lx.wav", mf->parent, ssrc); | |||
| ret->output = output_new(buf); | |||
| g_hash_table_insert(mf->ssrc_hash, GUINT_TO_POINTER(ssrc), ret); | |||
| out: | |||
| pthread_mutex_lock(&ret->lock); | |||
| pthread_mutex_unlock(&mf->lock); | |||
| return ret; | |||
| } | |||
| static gboolean ssrc_tree_get_first(void *key, void *val, void *data) { | |||
| packet_t **out = data; | |||
| *out = val; | |||
| return TRUE; | |||
| } | |||
| // ssrc is locked and must be unlocked when returning | |||
| static void ssrc_run(ssrc_t *ssrc) { | |||
| while (1) { | |||
| // inspect first packet to see if seq is correct | |||
| packet_t *first = NULL; | |||
| g_tree_foreach(ssrc->packets, ssrc_tree_get_first, &first); | |||
| if (!first) | |||
| break; | |||
| if (first->seq != ssrc->seq) | |||
| break; // need to wait for more | |||
| // determine payload type and run decoder | |||
| unsigned int payload_type = first->rtp->m_pt & 0x7f; | |||
| metafile_t *mf = ssrc->metafile; | |||
| pthread_mutex_lock(&mf->payloads_lock); | |||
| char *payload_str = mf->payload_types[payload_type]; | |||
| pthread_mutex_unlock(&mf->payloads_lock); | |||
| dbg("processing packet seq %i, payload type is %s", first->seq, payload_str); | |||
| g_tree_steal(ssrc->packets, first); | |||
| // check if we have a decoder for this payload type yet | |||
| if (!ssrc->decoders[payload_type]) | |||
| ssrc->decoders[payload_type] = decoder_new(payload_type, payload_str); | |||
| // XXX error handling | |||
| decoder_input(ssrc->decoders[payload_type], &first->payload, ntohl(first->rtp->timestamp), | |||
| ssrc->output); | |||
| packet_free(first); | |||
| dbg("packets left in queue: %i", g_tree_nnodes(ssrc->packets)); | |||
| ssrc->seq = (ssrc->seq + 1) & 0xffff; | |||
| } | |||
| pthread_mutex_unlock(&ssrc->lock); | |||
| } | |||
| // stream is unlocked, buf is malloc'd | |||
| void packet_process(stream_t *stream, unsigned char *buf, unsigned len) { | |||
| packet_t *packet = g_slice_alloc0(sizeof(*packet)); | |||
| packet->buffer = buf; // handing it over | |||
| // XXX more checking here | |||
| str bufstr; | |||
| str_init_len(&bufstr, packet->buffer, len); | |||
| packet->ip = (void *) bufstr.s; | |||
| // XXX kernel already does this - add metadata? | |||
| if (packet->ip->version == 4) { | |||
| if (str_shift(&bufstr, packet->ip->ihl << 2)) | |||
| goto err; | |||
| } | |||
| else { | |||
| packet->ip = NULL; | |||
| packet->ip6 = (void *) bufstr.s; | |||
| if (str_shift(&bufstr, sizeof(*packet->ip6))) | |||
| goto err; | |||
| } | |||
| packet->udp = (void *) bufstr.s; | |||
| str_shift(&bufstr, sizeof(*packet->udp)); | |||
| if (rtp_payload(&packet->rtp, &packet->payload, &bufstr)) | |||
| goto err; | |||
| if (rtp_padding(packet->rtp, &packet->payload)) | |||
| goto err; | |||
| packet->seq = ntohs(packet->rtp->seq_num); | |||
| dbg("packet parsed successfully, seq %u", packet->seq); | |||
| // insert into ssrc queue | |||
| ssrc_t *ssrc = ssrc_get(stream->metafile, ntohl(packet->rtp->ssrc)); | |||
| // check seq for dupes | |||
| if (G_UNLIKELY(ssrc->seq == -1)) { | |||
| // first packet we see | |||
| ssrc->seq = packet->seq; | |||
| goto seq_ok; | |||
| } | |||
| int diff = packet->seq - ssrc->seq; | |||
| if (diff >= 0x8000) | |||
| goto dupe; | |||
| if (diff < 0 && diff > -0x8000) | |||
| goto dupe; | |||
| // seq ok - fall thru | |||
| seq_ok: | |||
| if (g_tree_lookup(ssrc->packets, packet)) | |||
| goto dupe; | |||
| g_tree_insert(ssrc->packets, packet, packet); | |||
| // got a new packet, run the decoder | |||
| ssrc_run(ssrc); | |||
| return; | |||
| dupe: | |||
| dbg("skipping dupe packet (new seq %i prev seq %i)", packet->seq, ssrc->seq); | |||
| pthread_mutex_unlock(&ssrc->lock); | |||
| return; | |||
| err: | |||
| ilog(LOG_WARN, "Failed to parse packet headers"); | |||
| packet_free(packet); | |||
| } | |||
| @ -0,0 +1,10 @@ | |||
| #ifndef _PACKET_H_ | |||
| #define _PACKET_H_ | |||
| #include "types.h" | |||
| void ssrc_free(void *p); | |||
| void packet_process(stream_t *, unsigned char *, unsigned len); | |||
| #endif | |||
| @ -0,0 +1 @@ | |||
| ../lib/rtplib.c | |||