mirror of
https://github.com/dyne/FreeJ.git
synced 2026-02-12 15:50:45 +01:00
312 lines
9.9 KiB
C
312 lines
9.9 KiB
C
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <shout/shout.h>
|
|
#include <theora/theora.h>
|
|
|
|
#define MNL ""
|
|
|
|
#define OBJC
|
|
|
|
#ifdef OBJC
|
|
// ObjC includes to use pixelBuffers
|
|
#import <Carbon/Carbon.h>
|
|
#import <QuickTime/QuickTime.h>
|
|
|
|
CVPixelBufferRef sourcePixelBuffer ;
|
|
#endif
|
|
|
|
extern int want_quiet;
|
|
|
|
|
|
ogg_stream_state myGlob_to;
|
|
theora_state myGlob_td;
|
|
ogg_page myGlob_og;
|
|
yuv_buffer myGlob_yuv;
|
|
shout_t *myShout = NULL;
|
|
|
|
int video_x=0;
|
|
int video_y=0;
|
|
int frame_x=0;
|
|
int frame_y=0;
|
|
int frame_x_offset=0;
|
|
int frame_y_offset=0;
|
|
|
|
// private
|
|
int myOggfwd_process(ogg_page inputVideoPage) {
|
|
if (myShout == NULL) return -1;
|
|
if (inputVideoPage.header_len == 0 || inputVideoPage.body_len == 0) {
|
|
fprintf(stderr, MNL "!!!!!!!! oggfwd - buffer empty\n") ;
|
|
return -1;
|
|
}
|
|
if (shout_send(myShout, inputVideoPage.header, inputVideoPage.header_len) != SHOUTERR_SUCCESS) {
|
|
fprintf(stderr, MNL "OGGFWD - PH (error : %s) \n", shout_get_error(myShout)) ;
|
|
return -1;
|
|
}
|
|
if (shout_send(myShout, inputVideoPage.body, inputVideoPage.body_len) != SHOUTERR_SUCCESS) {
|
|
fprintf(stderr, MNL "OGGFWD - PB (error : %s) \n", shout_get_error(myShout)) ;
|
|
return -1;
|
|
}
|
|
|
|
if(shout_queuelen(myShout) > 0) fprintf(stderr, MNL "OGGFWD - queue length: %d\n", shout_queuelen(myShout));
|
|
|
|
shout_sync(myShout); // Sleep until the server will be ready for more data.
|
|
return 0;
|
|
}
|
|
|
|
#define OGGERR(error,param) { fprintf(stderr, MNL "OGGFWD - Error - %s [%s] (%s)\n", error, param, shout_get_error(myShout)); return -1;}
|
|
int myOggfwd_init( const char* outIceIp, int outIcePort, const char* outIcePass, const char* outIceMount,
|
|
const char *description, const char *genre, const char *name, const char *url ) {
|
|
|
|
unsigned short port = outIcePort ;
|
|
|
|
if ((myShout = shout_new()) == NULL) {
|
|
printf(MNL "OGGFWD - Error - allocate failed.\n") ;
|
|
return 0;
|
|
}
|
|
if (shout_set_host(myShout, outIceIp) != SHOUTERR_SUCCESS) OGGERR("invalid hostname", outIceIp);
|
|
if (shout_set_port(myShout, port) != SHOUTERR_SUCCESS) OGGERR("invalid portnumber", "");
|
|
if (shout_set_password(myShout, outIcePass) != SHOUTERR_SUCCESS) OGGERR("invalid password", "");
|
|
if (shout_set_mount(myShout, outIceMount) != SHOUTERR_SUCCESS) OGGERR("invalid mountpoint", outIceMount);
|
|
|
|
shout_set_format(myShout, SHOUT_FORMAT_OGG);
|
|
shout_set_public(myShout, 1);
|
|
|
|
if (description) shout_set_description(myShout, description);
|
|
if (genre) shout_set_genre(myShout, genre);
|
|
if (name) shout_set_name(myShout, name);
|
|
if (url) shout_set_url(myShout, url);
|
|
|
|
if (shout_open(myShout) == SHOUTERR_SUCCESS) {
|
|
if (!want_quiet)
|
|
fprintf(stderr, "OGGFWD - Connected to server (%s)\n", outIceIp) ;
|
|
return 1;
|
|
}
|
|
if (!want_quiet)
|
|
fprintf(stderr, "OGGFWD - Error connecting to server (%s)\n", outIceIp) ;
|
|
return 0;
|
|
}
|
|
|
|
void encoder_init(int inW, int inH, int inFramerate, int in_video_r, int in_video_q) {
|
|
printf("THEORA - Encoder Video Size: %dx%d\n", inW, inH) ;
|
|
frame_x = inW ;
|
|
frame_y = inH ;
|
|
|
|
ogg_stream_init(&myGlob_to, rand());
|
|
|
|
/* Theora has a divisible-by-sixteen restriction for the encoded video size
|
|
scale the frame size up to the nearest /16 and calculate offsets */
|
|
video_x=((frame_x + 15) >>4)<<4;
|
|
video_y=((frame_y + 15) >>4)<<4;
|
|
frame_x_offset=((video_x-frame_x)/2)&~1;
|
|
frame_y_offset=((video_y-frame_y)/2)&~1;
|
|
|
|
myGlob_yuv.y_width = video_x;
|
|
myGlob_yuv.y_height= video_y;
|
|
myGlob_yuv.y_stride= video_x;
|
|
|
|
myGlob_yuv.uv_width=video_x/2;
|
|
myGlob_yuv.uv_height=video_y/2;
|
|
myGlob_yuv.uv_stride=video_x/2;
|
|
|
|
printf("THEORA - video: %dx%d +%d+%d\n", video_x, video_y, frame_x_offset, frame_y_offset);
|
|
|
|
theora_info myGlob_ti;
|
|
theora_comment myGlob_tc;
|
|
ogg_packet myGlob_op;
|
|
|
|
theora_info_init(&myGlob_ti);
|
|
myGlob_ti.width= video_x;
|
|
myGlob_ti.height= video_y;
|
|
myGlob_ti.frame_width= frame_x;
|
|
myGlob_ti.frame_height= frame_y;
|
|
myGlob_ti.offset_x= frame_x_offset;
|
|
myGlob_ti.offset_y= frame_y_offset;
|
|
myGlob_ti.fps_numerator= inFramerate ; //video_hzn;
|
|
myGlob_ti.fps_denominator= 1 ;
|
|
myGlob_ti.aspect_numerator= 1 ;
|
|
myGlob_ti.aspect_denominator= 1 ;
|
|
myGlob_ti.colorspace=OC_CS_UNSPECIFIED;
|
|
myGlob_ti.colorspace=OC_CS_ITU_REC_470BG;
|
|
myGlob_ti.pixelformat=OC_PF_420;
|
|
myGlob_ti.target_bitrate= in_video_r ;
|
|
myGlob_ti.quality= in_video_q ;
|
|
|
|
myGlob_ti.dropframes_p=0;
|
|
myGlob_ti.quick_p=1;
|
|
myGlob_ti.keyframe_auto_p=1;
|
|
myGlob_ti.keyframe_frequency=30;
|
|
myGlob_ti.keyframe_frequency_force=myGlob_ti.keyframe_frequency;
|
|
myGlob_ti.keyframe_data_target_bitrate= myGlob_ti.target_bitrate * 4;
|
|
myGlob_ti.keyframe_auto_threshold=80;
|
|
myGlob_ti.keyframe_mindistance=8;
|
|
myGlob_ti.noise_sensitivity=1 ;
|
|
myGlob_ti.sharpness=0; /* range 0-2, 0 sharp, 2 less sharp, less bandwidth */
|
|
|
|
theora_encode_init(&myGlob_td, &myGlob_ti);
|
|
theora_info_clear(&myGlob_ti);
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/* write the bitstream header packets with proper page interleave */
|
|
|
|
/* first packet will get its own page automatically */
|
|
theora_encode_header(&myGlob_td, &myGlob_op);
|
|
ogg_stream_packetin(&myGlob_to, &myGlob_op);
|
|
|
|
if(ogg_stream_pageout(&myGlob_to, &myGlob_og)!=1){
|
|
fprintf(stderr, "Internal Ogg library error.\n");
|
|
return; // XXX FATAL
|
|
}
|
|
|
|
/* send header to icecas t*/
|
|
myOggfwd_process(myGlob_og) ;
|
|
|
|
/* create the remaining theora headers */
|
|
theora_comment_init(&myGlob_tc);
|
|
theora_encode_comment(&myGlob_tc, &myGlob_op);
|
|
ogg_stream_packetin(&myGlob_to, &myGlob_op);
|
|
/*theora_encode_comment() doesn't take a theora_state parameter, so it has to
|
|
allocate its own buffer to pass back the packet data.
|
|
If we don't free it here, we'll leak.
|
|
libogg2 makes this much cleaner: the stream owns the buffer after you call
|
|
packetin in libogg2, but this is not true in libogg1.*/
|
|
free(myGlob_op.packet);
|
|
theora_encode_tables(&myGlob_td, &myGlob_op);
|
|
ogg_stream_packetin(&myGlob_to, &myGlob_op);
|
|
|
|
/* Flush the rest of our headers. This ensures
|
|
the actual data in each stream will start
|
|
on a new page, as per spec. */
|
|
while(1){
|
|
int result = ogg_stream_flush(&myGlob_to, &myGlob_og);
|
|
if(result<0){
|
|
fprintf(stderr, MNL "THEORA - OGG stream flush error.\n");
|
|
return; // XXX FATAL.
|
|
}
|
|
if(result==0) break ;
|
|
myOggfwd_process(myGlob_og) ;
|
|
}
|
|
fprintf(stderr, "THEORA - encoder initialized. (framerate=%d,bitrate=%d,quality=%d)\n",inFramerate, in_video_r, in_video_q);
|
|
/* setup complete. Raw processing loop */
|
|
}
|
|
|
|
// private
|
|
void encode_video(uint8_t *theBuffer, int eos) {
|
|
ogg_packet op;
|
|
#if 0
|
|
myGlob_yuv.y= theBuffer;
|
|
myGlob_yuv.u= myGlob_yuv.y + video_x*video_y;
|
|
myGlob_yuv.v= myGlob_yuv.u + video_x*video_y/4;
|
|
#endif
|
|
theora_encode_YUVin(&myGlob_td, &myGlob_yuv);
|
|
while(theora_encode_packetout (&myGlob_td, eos, &op))
|
|
ogg_stream_packetin(&myGlob_to, &op);
|
|
}
|
|
|
|
|
|
int encoder_loop(uint8_t *imBuffRef) {
|
|
encode_video(imBuffRef, 0);
|
|
|
|
while(ogg_stream_pageout(&myGlob_to, &myGlob_og)>0) {
|
|
theora_granule_time(&myGlob_td, ogg_page_granulepos(&myGlob_og));
|
|
if (myOggfwd_process(myGlob_og)<0) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void myOggfwd_close() {
|
|
if (myShout == NULL) return;
|
|
shout_close(myShout);
|
|
myShout = NULL;
|
|
}
|
|
|
|
int encoder_close() {
|
|
ogg_stream_clear(&myGlob_to);
|
|
theora_clear(&myGlob_td);
|
|
return(0);
|
|
}
|
|
|
|
|
|
|
|
// WRAPPER OSX
|
|
|
|
#ifdef OBJC
|
|
int yuv_copy__argb_to_420(void *b_rgb, SInt32 b_rgb_stride, size_t width, size_t height, size_t offset_x, size_t offset_y, yuv_buffer *dst)
|
|
{
|
|
// TODO: offset ! & strides
|
|
|
|
#define _CR ((float)((bptr[(4*i)+1])&0xff))
|
|
#define _CG ((float)((bptr[(4*i)+2])&0xff))
|
|
#define _CB ((float)((bptr[(4*i)+3])&0xff))
|
|
|
|
#define _XCR ((float)( ( ((bptr[(4*i)+1])&0xff) + ((bptr[(4*(i+1))+1])&0xff) + ((bptr[(4*(i+1+width))+1])&0xff) + ((bptr[(4*(i+1+width))+1])&0xff) )>>2))
|
|
#define _XCG ((float)( ( ((bptr[(4*i)+2])&0xff) + ((bptr[(4*(i+1))+2])&0xff) + ((bptr[(4*(i+1+width))+2])&0xff) + ((bptr[(4*(i+1+width))+2])&0xff) )>>2))
|
|
#define _XCB ((float)( ( ((bptr[(4*i)+3])&0xff) + ((bptr[(4*(i+1))+3])&0xff) + ((bptr[(4*(i+1+width))+3])&0xff) + ((bptr[(4*(i+1+width))+3])&0xff) )>>2))
|
|
|
|
uint8_t *bptr = (uint8_t*) b_rgb;
|
|
int i; int c=0;
|
|
for (i=0;i<width*height;i++) {
|
|
double Y = (0.299 * _CR) + (0.587 * _CG) + (0.114 * _CB);
|
|
if (Y<1) dst->y[i]=0;
|
|
else if (Y>255) dst->y[i]=255;
|
|
else dst->y[i]=(uint8_t) floor(Y+.5);
|
|
#if 1
|
|
if (i%2==0 && ((i/width)%2)==0 && i < (width-1)*height) {
|
|
double V = (0.500 * _XCR) - (0.419 * _XCG) - (0.081 * _XCB) + 128;
|
|
double U = -(0.169 * _XCR) - (0.331 * _XCG) + (0.500 * _XCB) + 128;
|
|
|
|
if (U<1) dst->u[c]=0;
|
|
else if (U>254) dst->u[c]=255;
|
|
else dst->u[c]=(uint8_t) floor(U+.5);
|
|
|
|
if (V<1) dst->v[c]=0;
|
|
else if (V>254) dst->v[c]=255;
|
|
else dst->v[c]=(uint8_t) floor(V+.5);
|
|
c++;
|
|
}
|
|
#endif
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void encoder_example_init(int inW, int inH, int inFramerate, int in_video_r, int in_video_q) {
|
|
encoder_init(inW, inH, inFramerate, in_video_r, in_video_q);
|
|
myGlob_yuv.y= (uint8_t*) malloc(inW*inH*sizeof(uint8_t)*3/2);
|
|
myGlob_yuv.u= myGlob_yuv.y + video_x*video_y;
|
|
myGlob_yuv.v= myGlob_yuv.u + video_x*video_y/4;
|
|
}
|
|
|
|
int encoder_example_loop( CVPixelBufferRef theBuffer ) {
|
|
size_t pxwidth = CVPixelBufferGetWidth(theBuffer) ;
|
|
size_t pxheight = CVPixelBufferGetHeight(theBuffer) ;
|
|
CVPixelBufferLockBaseAddress(theBuffer, 0);
|
|
int err = yuv_copy__argb_to_420(
|
|
CVPixelBufferGetBaseAddress(theBuffer),
|
|
CVPixelBufferGetBytesPerRow(theBuffer),
|
|
pxwidth, pxheight,
|
|
frame_x_offset, frame_y_offset,
|
|
&myGlob_yuv);
|
|
|
|
CVPixelBufferUnlockBaseAddress( theBuffer, 0 );
|
|
return encoder_loop(myGlob_yuv.y)?0:1;
|
|
}
|
|
|
|
int encoder_example_end() {
|
|
encoder_close();
|
|
if (myGlob_yuv.y) free(myGlob_yuv.y);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|