mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
304 lines
8.0 KiB
C
304 lines
8.0 KiB
C
/** media.c
|
|
*
|
|
* Audio and video codec intitialization, encoding/decoding and playback
|
|
*
|
|
* Copyright (C) 2013 Tox project All Rights Reserved.
|
|
*
|
|
* This file is part of Tox.
|
|
*
|
|
* Tox is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Tox is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Tox. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
|
|
#include "rtp.h"
|
|
#include "media.h"
|
|
|
|
struct jitter_buffer {
|
|
RTPMessage **queue;
|
|
uint16_t capacity;
|
|
uint16_t size;
|
|
uint16_t front;
|
|
uint16_t rear;
|
|
uint8_t queue_ready;
|
|
uint16_t current_id;
|
|
uint32_t current_ts;
|
|
uint8_t id_set;
|
|
};
|
|
|
|
int empty_queue(struct jitter_buffer *q)
|
|
{
|
|
while (q->size > 0) {
|
|
rtp_free_msg(NULL, q->queue[q->front]);
|
|
q->front++;
|
|
|
|
if (q->front == q->capacity)
|
|
q->front = 0;
|
|
|
|
q->size--;
|
|
}
|
|
|
|
q->id_set = 0;
|
|
q->queue_ready = 0;
|
|
return 0;
|
|
}
|
|
|
|
struct jitter_buffer *create_queue(int capacity)
|
|
{
|
|
struct jitter_buffer *q;
|
|
q = calloc(sizeof(struct jitter_buffer), 1);
|
|
q->queue = calloc(sizeof(RTPMessage *), capacity);
|
|
q->size = 0;
|
|
q->capacity = capacity;
|
|
q->front = 0;
|
|
q->rear = -1;
|
|
q->queue_ready = 0;
|
|
q->current_id = 0;
|
|
q->current_ts = 0;
|
|
q->id_set = 0;
|
|
return q;
|
|
}
|
|
|
|
/* returns 1 if 'a' has a higher sequence number than 'b' */
|
|
uint8_t sequence_number_older(uint16_t sn_a, uint16_t sn_b, uint32_t ts_a, uint32_t ts_b)
|
|
{
|
|
/* TODO: There is already this kind of function in toxrtp.c.
|
|
* Maybe merge?
|
|
*/
|
|
return (sn_a > sn_b || ts_a > ts_b);
|
|
}
|
|
|
|
/* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */
|
|
RTPMessage *dequeue(struct jitter_buffer *q, int *success)
|
|
{
|
|
if (q->size == 0 || q->queue_ready == 0) { /* Empty queue */
|
|
q->queue_ready = 0;
|
|
*success = 0;
|
|
return NULL;
|
|
}
|
|
|
|
int front = q->front;
|
|
|
|
if (q->id_set == 0) {
|
|
q->current_id = q->queue[front]->header->sequnum;
|
|
q->current_ts = q->queue[front]->header->timestamp;
|
|
q->id_set = 1;
|
|
} else {
|
|
int next_id = q->queue[front]->header->sequnum;
|
|
int next_ts = q->queue[front]->header->timestamp;
|
|
|
|
/* if this packet is indeed the expected packet */
|
|
if (next_id == (q->current_id + 1) % MAX_SEQU_NUM) {
|
|
q->current_id = next_id;
|
|
q->current_ts = next_ts;
|
|
} else {
|
|
if (sequence_number_older(next_id, q->current_id, next_ts, q->current_ts)) {
|
|
/*printf("nextid: %d current: %d\n", next_id, q->current_id);*/
|
|
q->current_id = (q->current_id + 1) % MAX_SEQU_NUM;
|
|
*success = 2; /* tell the decoder the packet is lost */
|
|
return NULL;
|
|
} else {
|
|
/* packet too old */
|
|
/*printf("packet too old\n");*/
|
|
*success = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
q->size--;
|
|
q->front++;
|
|
|
|
if (q->front == q->capacity)
|
|
q->front = 0;
|
|
|
|
*success = 1;
|
|
q->current_id = q->queue[front]->header->sequnum;
|
|
q->current_ts = q->queue[front]->header->timestamp;
|
|
return q->queue[front];
|
|
}
|
|
|
|
|
|
int queue(struct jitter_buffer *q, RTPMessage *pk)
|
|
{
|
|
if (q->size == q->capacity) { /* Full, empty queue */
|
|
empty_queue(q);
|
|
/*rtp_free_msg(NULL, pk);*/
|
|
return 0;
|
|
}
|
|
|
|
if (q->size > 8)
|
|
q->queue_ready = 1;
|
|
|
|
++q->size;
|
|
++q->rear;
|
|
|
|
if (q->rear == q->capacity)
|
|
q->rear = 0;
|
|
|
|
q->queue[q->rear] = pk;
|
|
|
|
int a;
|
|
int b;
|
|
int j;
|
|
a = q->rear;
|
|
|
|
for (j = 0; j < q->size - 1; ++j) {
|
|
b = a - 1;
|
|
|
|
if (b < 0)
|
|
b += q->capacity;
|
|
|
|
if (sequence_number_older(q->queue[b]->header->sequnum, q->queue[a]->header->sequnum,
|
|
q->queue[b]->header->timestamp, q->queue[a]->header->timestamp)) {
|
|
RTPMessage *temp;
|
|
temp = q->queue[a];
|
|
q->queue[a] = q->queue[b];
|
|
q->queue[b] = temp;
|
|
/*printf("had to swap\n");*/
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
a -= 1;
|
|
|
|
if (a < 0)
|
|
a += q->capacity;
|
|
}
|
|
|
|
if (pk)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int init_video_decoder(CodecState *cs)
|
|
{
|
|
if (vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0,
|
|
VPX_DECODER_ABI_VERSION) != VPX_CODEC_OK) {
|
|
fprintf(stderr, "Init video_decoder failed!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int init_audio_decoder(CodecState *cs, uint32_t audio_channels)
|
|
{
|
|
int rc;
|
|
cs->audio_decoder = opus_decoder_create(cs->audio_sample_rate, audio_channels, &rc );
|
|
|
|
if ( rc != OPUS_OK ) {
|
|
fprintf(stderr, "Error while starting audio decoder!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int init_video_encoder(CodecState *cs, uint16_t width, uint16_t height, uint32_t video_bitrate)
|
|
{
|
|
vpx_codec_enc_cfg_t cfg;
|
|
int res = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
|
|
|
|
if (res) {
|
|
fprintf(stderr, "Failed to get config: %s\n", vpx_codec_err_to_string(res));
|
|
return -1;
|
|
}
|
|
|
|
cfg.rc_target_bitrate = video_bitrate;
|
|
cfg.g_w = width;
|
|
cfg.g_h = height;
|
|
|
|
if (vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0,
|
|
VPX_ENCODER_ABI_VERSION) != VPX_CODEC_OK) {
|
|
fprintf(stderr, "Failed to initialize encoder\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int init_audio_encoder(CodecState *cs, uint32_t audio_channels)
|
|
{
|
|
int err = OPUS_OK;
|
|
cs->audio_encoder = opus_encoder_create(cs->audio_sample_rate, audio_channels, OPUS_APPLICATION_AUDIO, &err);
|
|
err = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_bitrate));
|
|
err = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10));
|
|
|
|
|
|
return err == OPUS_OK ? 0 : -1;
|
|
}
|
|
|
|
|
|
CodecState *codec_init_session ( uint32_t audio_bitrate,
|
|
uint16_t audio_frame_duration,
|
|
uint32_t audio_sample_rate,
|
|
uint32_t audio_channels,
|
|
uint16_t video_width,
|
|
uint16_t video_height,
|
|
uint32_t video_bitrate )
|
|
{
|
|
CodecState *retu = calloc(sizeof(CodecState), 1);
|
|
assert(retu);
|
|
|
|
retu->audio_bitrate = audio_bitrate;
|
|
retu->audio_sample_rate = audio_sample_rate;
|
|
|
|
/* Encoders */
|
|
if (!video_width || !video_height) { /* Disable video */
|
|
/*video_width = 320;
|
|
video_height = 240; */
|
|
}
|
|
else {
|
|
retu->capabilities |= ( 0 == init_video_encoder(retu, video_width, video_height, video_bitrate) ) ? v_encoding : 0;
|
|
retu->capabilities |= ( 0 == init_video_decoder(retu) ) ? v_decoding : 0;
|
|
}
|
|
|
|
retu->capabilities |= ( 0 == init_audio_encoder(retu, audio_channels) ) ? a_encoding : 0;
|
|
retu->capabilities |= ( 0 == init_audio_decoder(retu, audio_channels) ) ? a_decoding : 0;
|
|
|
|
return retu;
|
|
}
|
|
|
|
void codec_terminate_session ( CodecState *cs )
|
|
{
|
|
if ( cs->audio_encoder )
|
|
opus_encoder_destroy(cs->audio_encoder);
|
|
|
|
if ( cs->audio_decoder )
|
|
opus_decoder_destroy(cs->audio_decoder);
|
|
|
|
|
|
/* TODO: Terminate video
|
|
* Do what???
|
|
*/
|
|
if ( cs->capabilities & v_decoding )
|
|
vpx_codec_destroy(&cs->v_decoder);
|
|
|
|
if ( cs->capabilities & v_encoding )
|
|
vpx_codec_destroy(&cs->v_encoder);
|
|
}
|