├── .gitignore ├── README ├── VideoRecorder.cpp ├── VideoRecorder.h └── build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | obj/ 5 | libs/ 6 | toolchain/ 7 | Makefile 8 | config.h 9 | config.status 10 | config.log 11 | *.lo 12 | *.la 13 | *.lai 14 | libtool 15 | stamp-h1 16 | faac 17 | x264 18 | ffmpeg 19 | # Mac OS X ignores 20 | .DS_Store 21 | ._* 22 | 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Android Video Recorder 2 | 3 | A simple library using ffmpeg, libx264, and libfaac to record videos on Android. 4 | Tested with Android NDK r6b. 5 | Targeted to armv7-a with vfpv3-d16. 6 | 7 | How To Build 8 | 9 | 1. Make directories faac, x264, and ffmpeg, and place the sources for them into the respective directories. 10 | 2. Run /path/to/android/ndk/build/tools/make-standalone-toolchain.sh --install-dir=./toolchain 11 | 3. Edit build.sh and change NDK at the top to point to your NDK directory. 12 | 4. Run the following commands: 13 | ./build.sh config faac 14 | ./build.sh compile faac 15 | ./build.sh config x264 16 | ./build.sh compile x264 17 | ./build.sh config ffmpeg 18 | ./build.sh compile ffmpeg 19 | ./build.sh compile recorder 20 | 5. You should now have a static library libVideoRecorder.a. 21 | 22 | How To Use 23 | 24 | Link libVideoRecorder.a into your Android JNI as a prebuilt static library. 25 | Use the interface given in VideoRecorder.h in your JNI C++ code. 26 | 27 | Legal 28 | 29 | Use at your own risk, the author is not responsible for anything. 30 | -------------------------------------------------------------------------------- /VideoRecorder.cpp: -------------------------------------------------------------------------------- 1 | // compiles on MacOS X with: g++ -DTESTING VideoRecorder.cpp -o v -lavcodec -lavformat -lavutil -lswscale -lx264 -g 2 | 3 | #ifdef ANDROID 4 | #include 5 | #define LOG(...) __android_log_print(ANDROID_LOG_INFO,"VideoRecorder",__VA_ARGS__) 6 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"VideoRecorder",__VA_ARGS__) 7 | #endif 8 | 9 | #ifdef TESTING 10 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 11 | #define LOGE(...) fprintf(stderr, __VA_ARGS__) 12 | #endif 13 | 14 | #include "VideoRecorder.h" 15 | 16 | extern "C" { 17 | #include 18 | #include 19 | #include 20 | } 21 | 22 | // Do not use C++ exceptions, templates, or RTTI 23 | 24 | namespace AVR { 25 | 26 | class VideoRecorderImpl : public VideoRecorder { 27 | public: 28 | VideoRecorderImpl(); 29 | ~VideoRecorderImpl(); 30 | 31 | bool SetVideoOptions(VideoFrameFormat fmt, int width, int height, unsigned long bitrate); 32 | bool SetAudioOptions(AudioSampleFormat fmt, int channels, unsigned long samplerate, unsigned long bitrate); 33 | 34 | bool Open(const char *mp4file, bool hasAudio, bool dbg); 35 | bool Close(); 36 | 37 | bool Start(); 38 | 39 | void SupplyVideoFrame(const void *frame, unsigned long numBytes, unsigned long timestamp); 40 | void SupplyAudioSamples(const void *samples, unsigned long numSamples); 41 | 42 | private: 43 | AVStream *add_audio_stream(enum CodecID codec_id); 44 | void open_audio(); 45 | void write_audio_frame(AVStream *st); 46 | 47 | AVStream *add_video_stream(enum CodecID codec_id); 48 | AVFrame *alloc_picture(enum PixelFormat pix_fmt, int width, int height); 49 | void open_video(); 50 | void write_video_frame(AVStream *st); 51 | 52 | // audio related vars 53 | int16_t *samples; 54 | uint8_t *audio_outbuf; 55 | int audio_outbuf_size; 56 | int audio_input_frame_size; 57 | AVStream *audio_st; 58 | 59 | unsigned long audio_input_leftover_samples; 60 | 61 | int audio_channels; // number of channels (2) 62 | unsigned long audio_bit_rate; // codec's output bitrate 63 | unsigned long audio_sample_rate; // number of samples per second 64 | int audio_sample_size; // size of each sample in bytes (16-bit = 2) 65 | AVSampleFormat audio_sample_format; 66 | 67 | // video related vars 68 | uint8_t *video_outbuf; 69 | int video_outbuf_size; 70 | AVStream *video_st; 71 | 72 | int video_width; 73 | int video_height; 74 | unsigned long video_bitrate; 75 | PixelFormat video_pixfmt; 76 | AVFrame *picture; // video frame after being converted to x264-friendly YUV420P 77 | AVFrame *tmp_picture; // video frame before conversion (RGB565) 78 | SwsContext *img_convert_ctx; 79 | 80 | unsigned long timestamp_base; 81 | 82 | // common 83 | AVFormatContext *oc; 84 | }; 85 | 86 | VideoRecorder::VideoRecorder() 87 | { 88 | 89 | } 90 | 91 | VideoRecorder::~VideoRecorder() 92 | { 93 | 94 | } 95 | 96 | VideoRecorderImpl::VideoRecorderImpl() 97 | { 98 | samples = NULL; 99 | audio_outbuf = NULL; 100 | audio_st = NULL; 101 | 102 | audio_input_leftover_samples = 0; 103 | 104 | video_outbuf = NULL; 105 | video_st = NULL; 106 | 107 | picture = NULL; 108 | tmp_picture = NULL; 109 | img_convert_ctx = NULL; 110 | 111 | oc = NULL; 112 | } 113 | 114 | VideoRecorderImpl::~VideoRecorderImpl() 115 | { 116 | 117 | } 118 | 119 | bool VideoRecorderImpl::Open(const char *mp4file, bool hasAudio, bool dbg) 120 | { 121 | av_register_all(); 122 | 123 | avformat_alloc_output_context2(&oc, NULL, NULL, mp4file); 124 | if (!oc) { 125 | LOGE("could not deduce output format from file extension\n"); 126 | return false; 127 | } 128 | 129 | video_st = add_video_stream(CODEC_ID_H264); 130 | 131 | if(hasAudio) 132 | audio_st = add_audio_stream(CODEC_ID_AAC); 133 | 134 | if(dbg) 135 | av_dump_format(oc, 0, mp4file, 1); 136 | 137 | open_video(); 138 | 139 | if(hasAudio) 140 | open_audio(); 141 | 142 | if (avio_open(&oc->pb, mp4file, AVIO_FLAG_WRITE) < 0) { 143 | LOGE("could not open '%s'\n", mp4file); 144 | return false; 145 | } 146 | 147 | av_write_header(oc); 148 | 149 | return true; 150 | } 151 | 152 | AVStream *VideoRecorderImpl::add_audio_stream(enum CodecID codec_id) 153 | { 154 | AVCodecContext *c; 155 | AVStream *st; 156 | 157 | st = av_new_stream(oc, 1); 158 | if (!st) { 159 | LOGE("could not alloc stream\n"); 160 | return NULL; 161 | } 162 | 163 | c = st->codec; 164 | c->codec_id = codec_id; 165 | c->codec_type = AVMEDIA_TYPE_AUDIO; 166 | c->sample_fmt = audio_sample_format; 167 | c->bit_rate = audio_bit_rate; 168 | c->sample_rate = audio_sample_rate; 169 | c->channels = audio_channels; 170 | c->profile = FF_PROFILE_AAC_LOW; 171 | 172 | if (oc->oformat->flags & AVFMT_GLOBALHEADER) 173 | c->flags |= CODEC_FLAG_GLOBAL_HEADER; 174 | 175 | return st; 176 | } 177 | 178 | void VideoRecorderImpl::open_audio() 179 | { 180 | AVCodecContext *c; 181 | AVCodec *codec; 182 | 183 | c = audio_st->codec; 184 | 185 | codec = avcodec_find_encoder(c->codec_id); 186 | if (!codec) { 187 | LOGE("audio codec not found\n"); 188 | return; 189 | } 190 | 191 | if (avcodec_open(c, codec) < 0) { 192 | LOGE("could not open audio codec\n"); 193 | return; 194 | } 195 | 196 | audio_outbuf_size = 10000; // XXX TODO 197 | audio_outbuf = (uint8_t *)av_malloc(audio_outbuf_size); 198 | 199 | audio_input_frame_size = c->frame_size; 200 | samples = (int16_t *)av_malloc(audio_input_frame_size * audio_sample_size * c->channels); 201 | 202 | audio_input_leftover_samples = 0; 203 | } 204 | 205 | AVStream *VideoRecorderImpl::add_video_stream(enum CodecID codec_id) 206 | { 207 | AVCodecContext *c; 208 | AVStream *st; 209 | 210 | st = avformat_new_stream(oc, NULL); 211 | if (!st) { 212 | LOGE("could not alloc stream\n"); 213 | return NULL; 214 | } 215 | 216 | c = st->codec; 217 | c->codec_id = codec_id; 218 | c->codec_type = AVMEDIA_TYPE_VIDEO; 219 | 220 | /* put sample parameters */ 221 | c->bit_rate = video_bitrate; 222 | c->width = video_width; 223 | c->height = video_height; 224 | c->time_base.num = 1; 225 | c->time_base.den = 90000; 226 | c->pix_fmt = PIX_FMT_YUV420P; // we convert everything to PIX_FMT_YUV420P 227 | 228 | /* h264 specific stuff */ 229 | /* c->coder_type = 0; // coder = 0 230 | c->me_cmp |= 1; // cmp=+chroma, where CHROMA = 1 231 | c->partitions |= X264_PART_I8X8 + X264_PART_I4X4 + X264_PART_P8X8 + X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8 232 | c->me_method = ME_HEX; // me_method=hex 233 | c->me_subpel_quality = 7; // subq=7 234 | c->me_range = 16; // me_range=16 235 | c->gop_size = 250; // g=250 236 | c->keyint_min = 25; // keyint_min=25 237 | c->scenechange_threshold = 40; // sc_threshold=40 238 | c->i_quant_factor = 0.71; // i_qfactor=0.71 239 | c->b_frame_strategy = 1; // b_strategy=1 240 | c->qcompress = 0.6; // qcomp=0.6 241 | c->qmin = 10; // qmin=10 242 | c->qmax = 51; // qmax=51 243 | c->max_qdiff = 4; // qdiff=4 244 | c->max_b_frames = 0; // bf=0 245 | c->refs = 3; // refs=3 246 | c->directpred = 1; // directpred=1 247 | c->trellis = 1; // trellis=1 248 | c->weighted_p_pred = 2; // wpredp=2 249 | 250 | c->flags |= CODEC_FLAG_LOOP_FILTER + CODEC_FLAG_GLOBAL_HEADER; 251 | c->flags2 |= CODEC_FLAG2_BPYRAMID + CODEC_FLAG2_MIXED_REFS + CODEC_FLAG2_WPRED + CODEC_FLAG2_8X8DCT + CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip 252 | c->flags2 |= CODEC_FLAG2_8X8DCT; 253 | c->flags2 ^= CODEC_FLAG2_8X8DCT;*/ 254 | 255 | /*x264 ultrafast preset*/ 256 | c->aq_mode = 0; // aq-mode = 0 257 | // b-adapt = 0 258 | c->max_b_frames = 0; // bframes = 0 259 | // no cabac 260 | // no deblock 261 | c->me_method = ME_HEX; // me = dia !!! 262 | c->partitions = 0; // partitions = none 263 | c->rc_lookahead = 0; // rc-lookahead = 0 264 | c->refs = 1; // ref = 1 265 | // scenecut = 0 266 | c->scenechange_threshold = 40; 267 | // subme = 0 268 | c->trellis = 0; // trellis = 0 269 | c->weighted_p_pred = 0; // weightp = 0 ?? 270 | c->coder_type = 0; 271 | c->me_subpel_quality = 4; 272 | c->me_range = 16; 273 | c->gop_size = 250; 274 | c->keyint_min = 25; 275 | c->i_quant_factor = 0.71; 276 | c->b_frame_strategy = 0; 277 | c->qcompress = 0.6; 278 | c->qmin = 10; 279 | c->qmax = 51; 280 | c->max_qdiff = 4; 281 | c->directpred = 0; 282 | c->flags |= CODEC_FLAG_LOOP_FILTER + CODEC_FLAG_GLOBAL_HEADER; 283 | c->flags2 |= CODEC_FLAG2_8X8DCT; c->flags2 ^= CODEC_FLAG2_8X8DCT; // no 8x8dct 284 | c->flags2 |= CODEC_FLAG2_MIXED_REFS; c->flags2 ^= CODEC_FLAG2_MIXED_REFS; // no mixed refs 285 | c->flags2 |= CODEC_FLAG2_MBTREE; c->flags2 ^= CODEC_FLAG2_MBTREE; // no mbtree 286 | c->flags2 |= CODEC_FLAG2_WPRED; c->flags2 ^= CODEC_FLAG2_WPRED; // no weightb ?? 287 | 288 | c->profile = FF_PROFILE_H264_BASELINE; 289 | //c->level = 30; 290 | 291 | if (oc->oformat->flags & AVFMT_GLOBALHEADER) 292 | c->flags |= CODEC_FLAG_GLOBAL_HEADER; 293 | 294 | return st; 295 | } 296 | 297 | AVFrame *VideoRecorderImpl::alloc_picture(enum PixelFormat pix_fmt, int width, int height) 298 | { 299 | AVFrame *pict; 300 | uint8_t *picture_buf; 301 | int size; 302 | 303 | pict = avcodec_alloc_frame(); 304 | if (!pict) { 305 | LOGE("could not allocate picture frame\n"); 306 | return NULL; 307 | } 308 | 309 | size = avpicture_get_size(pix_fmt, width, height); 310 | picture_buf = (uint8_t *)av_malloc(size); 311 | if (!picture_buf) { 312 | av_free(pict); 313 | LOGE("could not allocate picture frame buf\n"); 314 | return NULL; 315 | } 316 | avpicture_fill((AVPicture *)pict, picture_buf, 317 | pix_fmt, width, height); 318 | return pict; 319 | } 320 | 321 | void VideoRecorderImpl::open_video() 322 | { 323 | AVCodec *codec; 324 | AVCodecContext *c; 325 | 326 | timestamp_base = 0; 327 | 328 | if(!video_st) { 329 | LOGE("tried to open_video without a valid video_st (add_video_stream must have failed)\n"); 330 | return; 331 | } 332 | 333 | c = video_st->codec; 334 | 335 | codec = avcodec_find_encoder(c->codec_id); 336 | if (!codec) { 337 | LOGE("codec not found\n"); 338 | return; 339 | } 340 | 341 | if (avcodec_open(c, codec) < 0) { 342 | LOGE("could not open codec\n"); 343 | return; 344 | } 345 | 346 | video_outbuf = NULL; 347 | if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { 348 | video_outbuf_size = c->width * c->height * 4; // We assume the encoded frame will be smaller in size than an equivalent raw frame in RGBA8888 format ... a pretty safe assumption! 349 | video_outbuf = (uint8_t *)av_malloc(video_outbuf_size); 350 | if(!video_outbuf) { 351 | LOGE("could not allocate video_outbuf\n"); 352 | return; 353 | } 354 | } 355 | 356 | // the AVFrame the YUV frame is stored after conversion 357 | picture = alloc_picture(c->pix_fmt, c->width, c->height); 358 | if (!picture) { 359 | LOGE("Could not allocate picture\n"); 360 | return; 361 | } 362 | 363 | // the src AVFrame before conversion 364 | /*tmp_picture = alloc_picture(video_pixfmt, c->width, c->height); 365 | if (!tmp_picture) { 366 | LOGE("Could not allocate temporary picture\n"); 367 | return; 368 | }*/ 369 | // Instead of allocating the video frame buffer and attaching it tmp_picture, thereby incurring an unnecessary memcpy() in SupplyVideoFrame, 370 | // we only allocate the tmp_picture structure and set it up with default values. tmp_picture->data[0] is then reassigned to the incoming 371 | // frame data on the SupplyVideoFrame() call. 372 | tmp_picture = avcodec_alloc_frame(); 373 | if(!tmp_picture) { 374 | LOGE("Could not allocate temporary picture\n"); 375 | return; 376 | } 377 | 378 | if(video_pixfmt != PIX_FMT_RGB565LE) { 379 | LOGE("We've hardcoded linesize in tmp_picture for PIX_FMT_RGB565LE only!!\n"); 380 | return; 381 | } 382 | tmp_picture->linesize[0] = c->width * 2; // fix the linesize for tmp_picture (assuming RGB565) 383 | 384 | img_convert_ctx = sws_getContext(video_width, video_height, video_pixfmt, c->width, c->height, PIX_FMT_YUV420P, /*SWS_BICUBIC*/SWS_FAST_BILINEAR, NULL, NULL, NULL); 385 | if(img_convert_ctx==NULL) { 386 | LOGE("Could not initialize sws context\n"); 387 | return; 388 | } 389 | } 390 | 391 | bool VideoRecorderImpl::Close() 392 | { 393 | if(oc) { 394 | // flush out delayed frames 395 | AVPacket pkt; 396 | int out_size; 397 | AVCodecContext *c = video_st->codec; 398 | 399 | while(out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, NULL)) { 400 | av_init_packet(&pkt); 401 | 402 | if (c->coded_frame->pts != AV_NOPTS_VALUE) 403 | pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base); 404 | 405 | pkt.flags |= AV_PKT_FLAG_KEY; 406 | pkt.stream_index = video_st->index; 407 | pkt.data = video_outbuf; 408 | pkt.size = out_size; 409 | 410 | if(av_interleaved_write_frame(oc, &pkt) != 0) { 411 | LOGE("Unable to write video frame when flushing delayed frames\n"); 412 | return false; 413 | } 414 | else { 415 | LOG("wrote delayed frame of size %d\n", out_size); 416 | } 417 | } 418 | 419 | av_write_trailer(oc); 420 | } 421 | 422 | if(video_st) 423 | avcodec_close(video_st->codec); 424 | 425 | if(picture) { 426 | av_free(picture->data[0]); 427 | av_free(picture); 428 | } 429 | 430 | if(tmp_picture) { 431 | // tmp_picture->data[0] is no longer allocated by us 432 | //av_free(tmp_picture->data[0]); 433 | av_free(tmp_picture); 434 | } 435 | 436 | if(img_convert_ctx) { 437 | sws_freeContext(img_convert_ctx); 438 | } 439 | 440 | if(video_outbuf) 441 | av_free(video_outbuf); 442 | 443 | if(audio_st) 444 | avcodec_close(audio_st->codec); 445 | 446 | if(samples) 447 | av_free(samples); 448 | 449 | if(audio_outbuf) 450 | av_free(audio_outbuf); 451 | 452 | if(oc) { 453 | for(int i = 0; i < oc->nb_streams; i++) { 454 | av_freep(&oc->streams[i]->codec); 455 | av_freep(&oc->streams[i]); 456 | } 457 | avio_close(oc->pb); 458 | av_free(oc); 459 | } 460 | } 461 | 462 | bool VideoRecorderImpl::SetVideoOptions(VideoFrameFormat fmt, int width, int height, unsigned long bitrate) 463 | { 464 | switch(fmt) { 465 | case VideoFrameFormatYUV420P: video_pixfmt=PIX_FMT_YUV420P; break; 466 | case VideoFrameFormatNV12: video_pixfmt=PIX_FMT_NV12; break; 467 | case VideoFrameFormatNV21: video_pixfmt=PIX_FMT_NV21; break; 468 | case VideoFrameFormatRGB24: video_pixfmt=PIX_FMT_RGB24; break; 469 | case VideoFrameFormatBGR24: video_pixfmt=PIX_FMT_BGR24; break; 470 | case VideoFrameFormatARGB: video_pixfmt=PIX_FMT_ARGB; break; 471 | case VideoFrameFormatRGBA: video_pixfmt=PIX_FMT_RGBA; break; 472 | case VideoFrameFormatABGR: video_pixfmt=PIX_FMT_ABGR; break; 473 | case VideoFrameFormatBGRA: video_pixfmt=PIX_FMT_BGRA; break; 474 | case VideoFrameFormatRGB565LE: video_pixfmt=PIX_FMT_RGB565LE; break; 475 | case VideoFrameFormatRGB565BE: video_pixfmt=PIX_FMT_RGB565BE; break; 476 | case VideoFrameFormatBGR565LE: video_pixfmt=PIX_FMT_BGR565LE; break; 477 | case VideoFrameFormatBGR565BE: video_pixfmt=PIX_FMT_BGR565BE; break; 478 | default: LOGE("Unknown frame format passed to SetVideoOptions!\n"); return false; 479 | } 480 | video_width = width; 481 | video_height = height; 482 | video_bitrate = bitrate; 483 | return true; 484 | } 485 | 486 | bool VideoRecorderImpl::SetAudioOptions(AudioSampleFormat fmt, int channels, unsigned long samplerate, unsigned long bitrate) 487 | { 488 | switch(fmt) { 489 | case AudioSampleFormatU8: audio_sample_format=AV_SAMPLE_FMT_U8; audio_sample_size=1; break; 490 | case AudioSampleFormatS16: audio_sample_format=AV_SAMPLE_FMT_S16; audio_sample_size=2; break; 491 | case AudioSampleFormatS32: audio_sample_format=AV_SAMPLE_FMT_S32; audio_sample_size=4; break; 492 | case AudioSampleFormatFLT: audio_sample_format=AV_SAMPLE_FMT_FLT; audio_sample_size=4; break; 493 | case AudioSampleFormatDBL: audio_sample_format=AV_SAMPLE_FMT_DBL; audio_sample_size=8; break; 494 | default: LOGE("Unknown sample format passed to SetAudioOptions!\n"); return false; 495 | } 496 | audio_channels = channels; 497 | audio_bit_rate = bitrate; 498 | audio_sample_rate = samplerate; 499 | return true; 500 | } 501 | 502 | bool VideoRecorderImpl::Start() 503 | { 504 | 505 | } 506 | 507 | void VideoRecorderImpl::SupplyAudioSamples(const void *sampleData, unsigned long numSamples) 508 | { 509 | // check whether there is any audio stream (hasAudio=true) 510 | if(audio_st == NULL) { 511 | LOGE("tried to supply an audio frame when no audio stream was present\n"); 512 | return; 513 | } 514 | 515 | AVCodecContext *c = audio_st->codec; 516 | 517 | uint8_t *samplePtr = (uint8_t *)sampleData; // using a byte pointer 518 | 519 | // numSamples is supplied by the codec.. should be c->frame_size (1024 for AAC) 520 | // if it's more we go through it c->frame_size samples at a time 521 | while(numSamples) { 522 | static AVPacket pkt; 523 | av_init_packet(&pkt); // need to init packet every time so all the values (such as pts) are re-initialized 524 | 525 | // if we have enough samples for a frame, we write out c->frame_size number of samples (ie: one frame) to the output context 526 | if( (numSamples + audio_input_leftover_samples) >= c->frame_size) { 527 | // audio_input_leftover_samples contains the number of samples already in our "samples" array, left over from last time 528 | // we copy the remaining samples to fill up the frame to the complete frame size 529 | int num_new_samples = c->frame_size - audio_input_leftover_samples; 530 | 531 | memcpy((uint8_t *)samples + (audio_input_leftover_samples * audio_sample_size * audio_channels), samplePtr, num_new_samples * audio_sample_size * audio_channels); 532 | numSamples -= num_new_samples; 533 | samplePtr += (num_new_samples * audio_sample_size * audio_channels); 534 | audio_input_leftover_samples = 0; 535 | 536 | pkt.flags |= AV_PKT_FLAG_KEY; 537 | pkt.stream_index = audio_st->index; 538 | pkt.data = audio_outbuf; 539 | pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, samples); 540 | 541 | if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE) 542 | pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st->time_base); 543 | 544 | if(av_interleaved_write_frame(oc, &pkt) != 0) { 545 | LOGE("Error while writing audio frame\n"); 546 | return; 547 | } 548 | } 549 | else { 550 | // if we didn't have enough samples for a frame, we copy over however many we had and update audio_input_leftover_samples 551 | int num_new_samples = c->frame_size - audio_input_leftover_samples; 552 | if(numSamples < num_new_samples) 553 | num_new_samples = numSamples; 554 | 555 | memcpy((uint8_t *)samples + (audio_input_leftover_samples * audio_sample_size * audio_channels), samplePtr, num_new_samples * audio_sample_size * audio_channels); 556 | numSamples -= num_new_samples; 557 | samplePtr += (num_new_samples * audio_sample_size * audio_channels); 558 | audio_input_leftover_samples += num_new_samples; 559 | } 560 | } 561 | } 562 | 563 | void VideoRecorderImpl::SupplyVideoFrame(const void *frameData, unsigned long numBytes, unsigned long timestamp) 564 | { 565 | if(!video_st) { 566 | LOGE("tried to SupplyVideoFrame when no video stream was present\n"); 567 | return; 568 | } 569 | 570 | AVCodecContext *c = video_st->codec; 571 | 572 | //memcpy(tmp_picture->data[0], frameData, numBytes); 573 | // Don't copy the frame unnecessarily! Simply point tmp_picture->data[0] to the incoming frame 574 | tmp_picture->data[0]=(uint8_t*)frameData; 575 | 576 | // if the input pixel format is not YUV420P, we'll assume 577 | // it's stored in tmp_picture, so we'll convert it to YUV420P 578 | // and store it in "picture" 579 | // if it's already in YUV420P format we'll assume it's stored in 580 | // "picture" from before 581 | if(video_pixfmt != PIX_FMT_YUV420P) { 582 | sws_scale(img_convert_ctx, tmp_picture->data, tmp_picture->linesize, 0, video_height, picture->data, picture->linesize); 583 | } 584 | 585 | if(timestamp_base == 0) 586 | timestamp_base = timestamp; 587 | 588 | picture->pts = 90 * (timestamp - timestamp_base); // assuming millisecond timestamp and 90 kHz timebase 589 | 590 | int out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture); 591 | LOG("avcodec_encode_video returned %d\n", out_size); 592 | 593 | if(out_size > 0) { 594 | static AVPacket pkt; 595 | 596 | av_init_packet(&pkt); 597 | 598 | if (c->coded_frame->pts != AV_NOPTS_VALUE) 599 | pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base); 600 | 601 | if(c->coded_frame->key_frame) 602 | pkt.flags |= AV_PKT_FLAG_KEY; 603 | 604 | pkt.stream_index = video_st->index; 605 | pkt.data = video_outbuf; 606 | pkt.size = out_size; 607 | 608 | if(av_interleaved_write_frame(oc, &pkt) != 0) { 609 | LOGE("Unable to write video frame\n"); 610 | return; 611 | } 612 | } 613 | } 614 | 615 | VideoRecorder* VideoRecorder::New() 616 | { 617 | return (VideoRecorder*)(new VideoRecorderImpl); 618 | } 619 | 620 | } // namespace AVR 621 | 622 | #ifdef TESTING 623 | 624 | float t = 0; 625 | float tincr = 2 * M_PI * 110.0 / 44100; 626 | float tincr2 = 2 * M_PI * 110.0 / 44100 / 44100; 627 | 628 | void fill_audio_frame(int16_t *samples, int frame_size, int nb_channels) 629 | { 630 | int j, i, v; 631 | int16_t *q; 632 | 633 | q = samples; 634 | for (j = 0; j < frame_size; j++) { 635 | v = (int)(sin(t) * 10000); 636 | for(i = 0; i < nb_channels; i++) 637 | *q++ = v; 638 | t += tincr; 639 | tincr += tincr2; 640 | } 641 | } 642 | 643 | void fill_yuv_image(AVFrame *pict, int frame_index, int width, int height) 644 | { 645 | int x, y, i; 646 | 647 | i = frame_index; 648 | 649 | /* Y */ 650 | for (y = 0; y < height; y++) { 651 | for (x = 0; x < width; x++) { 652 | pict->data[0][y * pict->linesize[0] + x] = x + y + i * 3; 653 | } 654 | } 655 | 656 | /* Cb and Cr */ 657 | for (y = 0; y < height/2; y++) { 658 | for (x = 0; x < width/2; x++) { 659 | pict->data[1][y * pict->linesize[1] + x] = 128 + y + i * 2; 660 | pict->data[2][y * pict->linesize[2] + x] = 64 + x + i * 5; 661 | } 662 | } 663 | } 664 | 665 | #define RGB565(r,g,b) (uint16_t)( ((red & 0x1F) << 11) | ((green & 0x3F) << 5) | (blue & 0x1F) ) 666 | 667 | void fill_rgb_image(uint8_t *pixels, int i, int width, int height) 668 | { 669 | int x, y; 670 | 671 | for(y = 0; y < height; y++) { 672 | for(x = 0; x < width; x++) { 673 | 674 | uint8_t red = x + y + i * 3; 675 | uint8_t green = x + y + i * 3; 676 | uint8_t blue = x + y + i * 3; 677 | 678 | uint16_t pixel = RGB565(red, green, blue); 679 | 680 | // assume linesize is width*2 681 | pixels[y * (width*2) + x*2 + 0] = (uint8_t)(pixel); // lower order bits 682 | pixels[y * (width*2) + x*2 + 1] = (uint8_t)(pixel >> 8); // higher order bits 683 | } 684 | } 685 | } 686 | 687 | #include 688 | 689 | int main() 690 | { 691 | AVR::VideoRecorder *recorder = new AVR::VideoRecorderImpl(); 692 | 693 | recorder->SetAudioOptions(AVR::AudioSampleFormatS16, 2, 44100, 64000); 694 | recorder->SetVideoOptions(AVR::VideoFrameFormatRGB565LE, 640, 480, 400000); 695 | recorder->Open("testing.mp4", true, true); 696 | 697 | int16_t *sound_buffer = new int16_t[2048 * 2]; 698 | uint8_t *video_buffer = new uint8_t[640 * 480 * 2]; 699 | for(int i = 0; i < 200; i++) { 700 | fill_audio_frame(sound_buffer, 900, 2); 701 | recorder->SupplyAudioSamples(sound_buffer, 900); 702 | 703 | fill_rgb_image(video_buffer, i, 640, 480); 704 | recorder->SupplyVideoFrame(video_buffer, 640*480*2, (25 * i)+1); 705 | } 706 | 707 | delete video_buffer; 708 | delete sound_buffer; 709 | 710 | recorder->Close(); 711 | 712 | std::cout << "Done" << std::endl; 713 | 714 | delete recorder; 715 | 716 | return 0; 717 | } 718 | 719 | #endif /* TESTING */ 720 | -------------------------------------------------------------------------------- /VideoRecorder.h: -------------------------------------------------------------------------------- 1 | #ifndef _AVR_VIDEORECORDER_H_ 2 | #define _AVR_VIDEORECORDER_H_ 3 | 4 | // Encodes video to H.264 5 | // Encodes audio to AAC-LC 6 | // Outputs to MP4 file 7 | 8 | namespace AVR { 9 | 10 | enum VideoFrameFormat { 11 | VideoFrameFormatYUV420P=0, 12 | VideoFrameFormatNV12, 13 | VideoFrameFormatNV21, 14 | VideoFrameFormatRGB24, 15 | VideoFrameFormatBGR24, 16 | VideoFrameFormatARGB, 17 | VideoFrameFormatRGBA, 18 | VideoFrameFormatABGR, 19 | VideoFrameFormatBGRA, 20 | VideoFrameFormatRGB565LE, 21 | VideoFrameFormatRGB565BE, 22 | VideoFrameFormatBGR565LE, 23 | VideoFrameFormatBGR565BE, 24 | VideoFrameFormatMax 25 | }; 26 | 27 | enum AudioSampleFormat { 28 | AudioSampleFormatU8=0, 29 | AudioSampleFormatS16, 30 | AudioSampleFormatS32, 31 | AudioSampleFormatFLT, 32 | AudioSampleFormatDBL, 33 | AudioSampleFormatMax 34 | }; 35 | 36 | class VideoRecorder { 37 | public: 38 | VideoRecorder(); 39 | virtual ~VideoRecorder(); 40 | 41 | // Use this to get an instance of VideoRecorder. Use delete operator to delete it. 42 | static VideoRecorder* New(); 43 | 44 | // Return true on success, false on failure 45 | 46 | // Call these first 47 | virtual bool SetVideoOptions(VideoFrameFormat fmt,int width,int height,unsigned long bitrate)=0; 48 | virtual bool SetAudioOptions(AudioSampleFormat fmt,int channels,unsigned long samplerate,unsigned long bitrate)=0; 49 | 50 | // Call after SetVideoOptions/SetAudioOptions 51 | virtual bool Open(const char* mp4file,bool hasAudio,bool dbg)=0; 52 | // Call last 53 | virtual bool Close()=0; 54 | 55 | // After this succeeds, you can call SupplyVideoFrame and SupplyAudioSamples 56 | virtual bool Start()=0; 57 | 58 | // Supply a video frame 59 | virtual void SupplyVideoFrame(const void* frame,unsigned long numBytes,unsigned long timestamp)=0; 60 | // Supply audio samples 61 | virtual void SupplyAudioSamples(const void* samples,unsigned long numSamples)=0; 62 | }; 63 | 64 | } // namespace AVR 65 | 66 | #endif // _AVR_VIDEORECORDER_H_ -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set NDK to the full path to your NDK install 4 | NDK="/Users/Ayena/Desktop/android-ndk-r6b" 5 | 6 | DIR="$( cd "$( dirname "$0" )" && pwd )" 7 | 8 | export PATH=$PATH:$NDK:$DIR/toolchain/bin 9 | 10 | # Got these from watching verbose output of ndk-build 11 | # -fpic is not here because we enable it in the configure scripts 12 | # -fstack-protector is not here because it causes an ld error in configure scripts when they try to compile test executables 13 | ANDROID_CFLAGS="-DANDROID -D__ARM_ARCH_7__ -D__ARM_ARCH_7A__ -ffunction-sections -funwind-tables -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -Wa,--noexecstack -Os " 14 | ANDROID_CXXFLAGS="-DANDROID -D__ARM_ARCH_7__ -D__ARM_ARCH_7A__ -ffunction-sections -funwind-tables -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-exceptions -fno-rtti -mthumb -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -Wa,--noexecstack -Os " 15 | 16 | PREFIX=arm-linux-androideabi- 17 | export AR=${PREFIX}ar 18 | export AS=${PREFIX}gcc 19 | export CC=${PREFIX}gcc 20 | export CXX=${PREFIX}g++ 21 | export LD=${PREFIX}ld 22 | export NM=${PREFIX}nm 23 | export RANLIB=${PREFIX}ranlib 24 | export STRIP=${PREFIX}strip 25 | export CFLAGS=${ANDROID_CFLAGS} 26 | export CXXFLAGS=${ANDROID_CXXFLAGS} 27 | export CPPFLAGS=${ANDROID_CPPFLAGS} 28 | 29 | usage() 30 | { 31 | echo -e "Usage: $1 [config|compile] targets" 32 | echo -e "For config, targets are: faac x264 ffmpeg" 33 | echo -e "For compile, targets are: faac x264 ffmpeg recorder" 34 | echo -e "\tTargets must be compiled in that order due to dependencies." 35 | } 36 | 37 | config_clean() 38 | { 39 | make distclean &>/dev/null 40 | rm config.cache &>/dev/null 41 | rm config.log &>/dev/null 42 | } 43 | 44 | config_faac() 45 | { 46 | echo -e "Configuring FAAC" 47 | pushd faac 48 | config_clean 49 | ./configure --host=arm-linux \ 50 | --disable-dependency-tracking \ 51 | --disable-shared \ 52 | --enable-static \ 53 | --with-pic \ 54 | --without-mp4v2 55 | popd 56 | } 57 | 58 | config_x264() 59 | { 60 | echo -e "Configuring x264" 61 | pushd x264 62 | config_clean 63 | ./configure --host=arm-linux \ 64 | --disable-cli \ 65 | --enable-static \ 66 | --enable-pic 67 | popd 68 | } 69 | 70 | ffmpeg_config_fix() 71 | { 72 | echo -e "Fixing ffmpeg config.h" 73 | sed -i "" 's/HAVE_VFPV3 0/HAVE_VFPV3 1/g' config.h 74 | } 75 | 76 | config_ffmpeg() 77 | { 78 | echo -e "Configuring ffmpeg" 79 | pushd ffmpeg 80 | config_clean 81 | FFMPEG_ENCODERS="--enable-encoder=libfaac --enable-encoder=libx264" 82 | FFMPEG_DECODERS="" 83 | FFMPEG_MUXERS="--enable-muxer=mp4" 84 | FFMPEG_DEMUXERS="" 85 | FFMPEG_PARSERS="--enable-parser=aac --enable-parser=h264" 86 | FFMPEG_PROTOCOLS="--enable-protocol=file" 87 | FFMPEG_BSFS="--enable-bsf=aac_adtstoasc --enable-bsf=h264_mp4toannexb" 88 | ./configure --cross-prefix=arm-linux-androideabi- \ 89 | --enable-cross-compile \ 90 | --target-os=linux \ 91 | --arch=arm \ 92 | --enable-gpl \ 93 | --enable-version3 \ 94 | --enable-nonfree \ 95 | --enable-static \ 96 | --enable-pic \ 97 | --enable-small \ 98 | --disable-symver \ 99 | --disable-debug \ 100 | --disable-doc \ 101 | --disable-ffmpeg \ 102 | --disable-avconv \ 103 | --disable-ffplay \ 104 | --disable-ffprobe \ 105 | --disable-ffserver \ 106 | --disable-avdevice \ 107 | --disable-avfilter \ 108 | --disable-postproc \ 109 | --disable-everything \ 110 | $FFMPEG_ENCODERS \ 111 | $FFMPEG_DECODERS \ 112 | $FFMPEG_MUXERS \ 113 | $FFMPEG_DEMUXERS \ 114 | $FFMPEG_PARSERS \ 115 | $FFMPEG_PROTOCOLS \ 116 | $FFMPEG_BSFS \ 117 | --enable-zlib \ 118 | --enable-libx264 \ 119 | --enable-libfaac \ 120 | --extra-cflags="-Wno-deprecated-declarations -I$DIR/faac/include -I$DIR/x264" \ 121 | --extra-ldflags="-L$DIR/ffmpeg" 122 | if [ -f config.h ]; then ffmpeg_config_fix; fi 123 | popd 124 | } 125 | 126 | config() 127 | { 128 | case $1 in 129 | "faac") config_faac ;; 130 | "x264") config_x264 ;; 131 | "ffmpeg") config_ffmpeg ;; 132 | *) echo -e "Valid config targets are: faac x264 ffmpeg" ;; 133 | esac 134 | } 135 | 136 | compile_faac() 137 | { 138 | echo -e "Compiling FAAC" 139 | pushd faac 140 | make clean 141 | make 142 | cp -v libfaac/.libs/libfaac.a ../ffmpeg 143 | popd 144 | } 145 | 146 | compile_x264() 147 | { 148 | echo -e "Compiling x264" 149 | pushd x264 150 | make clean 151 | make 152 | cp -v libx264.a ../ffmpeg 153 | popd 154 | } 155 | 156 | compile_ffmpeg() 157 | { 158 | echo -e "Compiling ffmpeg" 159 | pushd ffmpeg 160 | make clean 161 | make 162 | $AR d libavcodec/libavcodec.a inverse.o 163 | mkdir tempobjs 164 | pushd tempobjs 165 | $LD -r --whole-archive ../libavcodec/libavcodec.a -o avcodec.o 166 | $LD -r --whole-archive ../libavformat/libavformat.a -o avformat.o 167 | $LD -r --whole-archive ../libavutil/libavutil.a -o avutil.o 168 | $LD -r --whole-archive ../libswresample/libswresample.a -o swresample.o 169 | $LD -r --whole-archive ../libswscale/libswscale.a -o swscale.o 170 | rm -rf ../libffmpeg.a 171 | $AR r ../libffmpeg.a *.o 172 | popd 173 | rm -rf tempobjs 174 | } 175 | 176 | compile_recorder() 177 | { 178 | echo -e "Compiling recorder" 179 | rm -f VideoRecorder.o 180 | $CXX $CXXFLAGS -O2 -D__STDC_CONSTANT_MACROS -Iffmpeg -fpic -c VideoRecorder.cpp -o VideoRecorder.o 181 | mkdir tempobjs 182 | pushd tempobjs 183 | $LD -r --whole-archive ../ffmpeg/libfaac.a -o faac.o 184 | $LD -r --whole-archive ../ffmpeg/libx264.a -o x264.o 185 | $LD -r --whole-archive ../ffmpeg/libffmpeg.a -o ffmpeg.o 186 | rm -rf ../libVideoRecorder.a 187 | $AR crsv ../libVideoRecorder.a *.o ../VideoRecorder.o 188 | popd 189 | rm -rf tempobjs 190 | } 191 | 192 | compile() 193 | { 194 | case $1 in 195 | "faac") compile_faac ;; 196 | "x264") compile_x264 ;; 197 | "ffmpeg") compile_ffmpeg ;; 198 | "recorder") compile_recorder ;; 199 | *) echo -e "Valid compile targets are: faac x264 ffmpeg lib" ;; 200 | esac 201 | } 202 | 203 | case "$1" in 204 | "") usage $0 ;; 205 | "config") config $2 ;; 206 | "compile") compile $2 ;; 207 | *) usage $0 ;; 208 | esac 209 | 210 | --------------------------------------------------------------------------------