@@ -32,8 +32,6 @@ using namespace std;
3232extern "C" {
3333
3434x264_t *encoder = NULL ;
35- x264_nal_t *nal;
36- int nnal;
3735int _width = -1 ;
3836int _height = -1 ;
3937int _colorformat = -1 ;
@@ -190,14 +188,16 @@ jint init_encoder(JNIEnv *env, jobject thiz, jobjectArray params, jint width,
190188
191189 x264Params.i_width = _width;
192190 x264Params.i_height = _height;
193- // TODO: remove this, currenlty the encoder is broken without it
194- x264Params.i_threads = 1 ;
195191 x264Params.i_csp = _colorformat;
196192 x264Params.i_bitdepth = _bitdepth;
197193 x264Params.i_fps_num = 30 ;
198194 x264Params.i_fps_den = 1 ;
199195 x264Params.i_timebase_num = 1 ;
200- x264Params.i_timebase_den = 1000000 ; // Nanosecs
196+ x264Params.i_timebase_den = 1000000 ; // Microsecs
197+ // Output NALs in Annex B format (start codes) - MediaMuxer handles conversion
198+ x264Params.b_annexb = 1 ;
199+ // Disable repeat headers - we handle SPS/PPS separately
200+ x264Params.b_repeat_headers = 0 ;
201201 LOGD (" Open x264 encoder" );
202202 encoder = x264_encoder_open (&x264Params);
203203 if (!encoder) {
@@ -219,6 +219,8 @@ jbyteArray get_header(JNIEnv *env, jobject thiz, jbyteArray headerArray) {
219219 return NULL ;
220220 }
221221
222+ x264_nal_t *nal;
223+ int nnal;
222224 int size_of_headers = x264_encoder_headers (encoder, &nal, &nnal);
223225 jbyte *buf = new jbyte[size_of_headers];
224226 memset (buf, 0 , size_of_headers);
@@ -246,6 +248,45 @@ jbyteArray get_header(JNIEnv *env, jobject thiz, jbyteArray headerArray) {
246248 return ret;
247249}
248250
251+ static int copy_nal_to_output (x264_nal_t *nal, int nnal, jbyte *output_data,
252+ int output_size) {
253+ // Start with 2-byte offset - required for MediaMuxer compatibility
254+ int offset = 2 ;
255+ output_data[0 ] = 0 ;
256+ output_data[1 ] = 0 ;
257+
258+ for (int i = 0 ; i < nnal; i++) {
259+ // Skip header NALs - they're handled separately via get_header()
260+ if (nal[i].i_type == NAL_SPS || nal[i].i_type == NAL_PPS ||
261+ nal[i].i_type == NAL_SEI || nal[i].i_type == NAL_AUD ||
262+ nal[i].i_type == NAL_FILLER) {
263+ continue ;
264+ }
265+ if (offset + nal[i].i_payload <= output_size) {
266+ memcpy (output_data + offset, nal[i].p_payload , nal[i].i_payload );
267+ offset += nal[i].i_payload ;
268+ } else {
269+ LOGE (" Output buffer too small for NAL unit" );
270+ }
271+ }
272+ return offset;
273+ }
274+
275+ static void update_frame_info (JNIEnv *env, jobject frameInfo,
276+ x264_picture_t *pic_out, int frame_size) {
277+ jclass infoClass = env->FindClass (" com/facebook/encapp/utils/FrameInfo" );
278+ jfieldID isIframeId = env->GetFieldID (infoClass, " mIsIframe" , " Z" );
279+ jfieldID ptsId = env->GetFieldID (infoClass, " mPts" , " J" );
280+ jfieldID dtsId = env->GetFieldID (infoClass, " mDts" , " J" );
281+ jfieldID sizeId = env->GetFieldID (infoClass, " mSize" , " J" );
282+
283+ env->SetLongField (frameInfo, sizeId, frame_size);
284+ env->SetLongField (frameInfo, ptsId, pic_out->i_pts );
285+ env->SetLongField (frameInfo, dtsId, pic_out->i_dts );
286+ env->SetBooleanField (frameInfo, isIframeId, pic_out->b_keyframe );
287+ }
288+
289+ // Returns frame size, 0 if buffered (B-frames), -1 on error
249290jint encode (JNIEnv *env, jobject thiz, jbyteArray input, jbyteArray output,
250291 jobject frameInfo) {
251292 LOGD (" Encoding frame" );
@@ -255,32 +296,33 @@ jint encode(JNIEnv *env, jobject thiz, jbyteArray input, jbyteArray output,
255296 }
256297
257298 jclass infoClass = env->FindClass (" com/facebook/encapp/utils/FrameInfo" );
258- jfieldID isIframeId = env->GetFieldID (infoClass, " mIsIframe" , " Z" );
259299 jfieldID ptsId = env->GetFieldID (infoClass, " mPts" , " J" );
260- jfieldID dtsId = env->GetFieldID (infoClass, " mDts" , " J" );
261- jfieldID sizeId = env->GetFieldID (infoClass, " mSize" , " J" );
262300
263- // All interaction must be done before locking java...
301+ x264_nal_t *nal;
302+ int nnal;
303+
264304 x264_picture_t pic_in = {0 };
265305 x264_picture_t pic_out = {0 };
266306
267- // TODO: We are assuming yuv420p, add check...
268- int ySize = _width * _height; // Stride?
307+ int ySize = _width * _height;
269308 int uvSize = (int )(ySize / 4 .0f );
309+ int inputSize = ySize + uvSize * 2 ;
270310
271311 x264_picture_init (&pic_in);
272312 pic_in.img .i_csp = _colorformat;
273- // TODO: hard code, really?
274313 pic_in.img .i_plane = 3 ;
275314
276315 long pts = env->GetLongField (frameInfo, ptsId);
277316 LOGD (" Set pts: %ld" , pts);
278- pic_in.i_pts = pts; // Convert to milliseconds
317+ pic_in.i_pts = pts;
279318
280- // Now we are locking java
281- jbyte *input_data = (jbyte *)env->GetPrimitiveArrayCritical (input, 0 );
282- jbyte *output_data = (jbyte *)env->GetPrimitiveArrayCritical (output, 0 );
319+ jsize input_array_size = env->GetArrayLength (input);
320+ jsize output_array_size = env->GetArrayLength (output);
283321
322+ // Use local buffers instead of GetPrimitiveArrayCritical to allow GC
323+ jbyte *input_data = new jbyte[inputSize];
324+ jbyte *output_data = new jbyte[output_array_size];
325+ env->GetByteArrayRegion (input, 0 , inputSize, input_data);
284326 pic_in.img .plane [0 ] = (uint8_t *)input_data;
285327 pic_in.img .plane [1 ] = (uint8_t *)(input_data + ySize);
286328 pic_in.img .plane [2 ] = (uint8_t *)(input_data + ySize + uvSize);
@@ -290,41 +332,68 @@ jint encode(JNIEnv *env, jobject thiz, jbyteArray input, jbyteArray output,
290332 pic_in.img .i_stride [2 ] = _width / 2 ;
291333
292334 int frame_size = x264_encoder_encode (encoder, &nal, &nnal, &pic_in, &pic_out);
293- if (frame_size >= 0 ) {
294- // TODO: Added total_size = 2 for debugging purpose
295- int total_size = 2 ;
296- for (int i = 0 ; i < nnal; i++) {
297- total_size += nal[i].i_payload ;
298- }
299- int offset = 2 ;
300- for (int i = 0 ; i < nnal; i++) {
301- if (nal[i].i_type == NAL_SPS || nal[i].i_type == NAL_PPS ||
302- nal[i].i_type == NAL_SEI || nal[i].i_type == NAL_AUD ||
303- nal[i].i_type == NAL_FILLER) {
304- continue ;
305- }
306- memcpy (output_data + offset, nal[i].p_payload , nal[i].i_payload );
307- offset += nal[i].i_payload ;
308- }
309- frame_size = total_size;
335+
336+ int total_size = 0 ;
337+ if (frame_size > 0 ) {
338+ total_size = copy_nal_to_output (nal, nnal, output_data, output_array_size);
339+ env->SetByteArrayRegion (output, 0 , total_size, output_data);
340+ update_frame_info (env, frameInfo, &pic_out, total_size);
341+ } else if (frame_size == 0 ) {
342+ // Frame buffered (B-frame reordering)
343+ LOGD (" Frame buffered, no output yet (encoder delay)" );
344+ update_frame_info (env, frameInfo, &pic_out, 0 );
345+ } else {
346+ LOGE (" x264_encoder_encode failed with error: %d" , frame_size);
310347 }
348+ delete[] input_data;
349+ delete[] output_data;
311350
312- env-> ReleasePrimitiveArrayCritical (input, input_data, 0 ) ;
313- env-> ReleasePrimitiveArrayCritical (output, output_data, 0 );
351+ return total_size ;
352+ }
314353
315- // Set data from the encoding process
316- env->SetLongField (frameInfo, sizeId, frame_size);
317- env->SetLongField (frameInfo, ptsId, pic_out.i_pts );
318- env->SetLongField (frameInfo, dtsId, pic_out.i_dts );
319- env->SetBooleanField (frameInfo, isIframeId, pic_out.b_keyframe );
320- // Do we need additional info?
321- // LOGD("Not saved: Pic type: %d", pic_out.i_type);
322- // TODO: we also have a complete list of params. Maybe with a debug flag we
323- // could push this to java?
324-
325- // x264_image_properties_t holds psnr and ssim as well (potentially, if
326- // enabled)
327- return frame_size;
354+ // Flush buffered frames. Call until returns 0.
355+ jint flush_encoder (JNIEnv *env, jobject thiz, jbyteArray output,
356+ jobject frameInfo) {
357+ LOGD (" Flushing encoder" );
358+ if (!encoder) {
359+ LOGI (" Encoder is not initialized for flushing" );
360+ return -1 ;
361+ }
362+
363+ x264_nal_t *nal;
364+ int nnal;
365+ x264_picture_t pic_out = {0 };
366+
367+ jsize output_array_size = env->GetArrayLength (output);
368+ jbyte *output_data = new jbyte[output_array_size];
369+
370+ // NULL input flushes buffered frames
371+ int frame_size =
372+ x264_encoder_encode (encoder, &nal, &nnal, NULL , &pic_out);
373+
374+ int total_size = 0 ;
375+ if (frame_size > 0 ) {
376+ total_size = copy_nal_to_output (nal, nnal, output_data, output_array_size);
377+ env->SetByteArrayRegion (output, 0 , total_size, output_data);
378+ update_frame_info (env, frameInfo, &pic_out, total_size);
379+ LOGD (" Flushed frame: pts=%ld, dts=%ld, size=%d" , (long )pic_out.i_pts ,
380+ (long )pic_out.i_dts , total_size);
381+ } else if (frame_size == 0 ) {
382+ LOGD (" Encoder flush complete, no more buffered frames" );
383+ update_frame_info (env, frameInfo, &pic_out, 0 );
384+ } else {
385+ LOGE (" x264_encoder_encode (flush) failed with error: %d" , frame_size);
386+ }
387+
388+ delete[] output_data;
389+ return total_size;
390+ }
391+
392+ jint get_delayed_frames (JNIEnv *env, jobject thiz) {
393+ if (!encoder) {
394+ return 0 ;
395+ }
396+ return x264_encoder_delayed_frames (encoder);
328397}
329398
330399void update_settings (JNIEnv *env, jobject thiz, jobjectArray params) {
@@ -470,6 +539,25 @@ jobjectArray get_all_settings(JNIEnv *env, jobject thiz) {
470539 parameterClass, paramConstructor, env->NewStringUTF (" i_timebase_den" ),
471540 env->NewStringUTF (" intType" ), env->NewStringUTF (buffer)));
472541
542+ snprintf (buffer, len, " %d" , info.i_threads );
543+ params.push_back (env->NewObject (
544+ parameterClass, paramConstructor, env->NewStringUTF (" i_threads" ),
545+ env->NewStringUTF (" intType" ), env->NewStringUTF (buffer)));
546+ snprintf (buffer, len, " %d" , info.i_lookahead_threads );
547+ params.push_back (env->NewObject (
548+ parameterClass, paramConstructor,
549+ env->NewStringUTF (" i_lookahead_threads" ), env->NewStringUTF (" intType" ),
550+ env->NewStringUTF (buffer)));
551+ snprintf (buffer, len, " %d" , info.b_sliced_threads );
552+ params.push_back (env->NewObject (
553+ parameterClass, paramConstructor, env->NewStringUTF (" b_sliced_threads" ),
554+ env->NewStringUTF (" intType" ), env->NewStringUTF (buffer)));
555+
556+ snprintf (buffer, len, " %d" , info.i_bframe );
557+ params.push_back (env->NewObject (
558+ parameterClass, paramConstructor, env->NewStringUTF (" i_bframe" ),
559+ env->NewStringUTF (" intType" ), env->NewStringUTF (buffer)));
560+
473561 jobjectArray ret = env->NewObjectArray (params.size (), parameterClass, NULL );
474562 int index = 0 ;
475563 for (auto element : params) {
@@ -492,6 +580,9 @@ static JNINativeMethod methods[] = {
492580 (void *)&init_encoder},
493581 {" getHeader" , " ()[B" , (void *)&get_header},
494582 {" encode" , " ([B[BLcom/facebook/encapp/utils/FrameInfo;)I" , (void *)&encode},
583+ {" flushEncoder" , " ([BLcom/facebook/encapp/utils/FrameInfo;)I" ,
584+ (void *)&flush_encoder},
585+ {" getDelayedFrames" , " ()I" , (void *)&get_delayed_frames},
495586 {" close" , " ()V" , (void *)&close},
496587 {" getAllEncoderSettings" , " ()[Lcom/facebook/encapp/utils/StringParameter;" ,
497588 (void *)&get_all_settings},
0 commit comments