| | |
| | | import android.media.MediaCodec; |
| | | import android.media.MediaCodecInfo; |
| | | import android.media.MediaFormat; |
| | | import android.util.Log; |
| | | import com.anyun.libusbcamera.UsbCamera; |
| | | |
| | | import java.io.File; |
| | |
| | | import java.nio.ByteBuffer; |
| | | import java.util.Arrays; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import timber.log.Timber; |
| | | |
| | | /** |
| | | * H264视频编码器 |
| | |
| | | */ |
| | | public void setEnableNetworkTransmission(boolean enable) { |
| | | this.enableNetworkTransmission = enable; |
| | | Log.d(TAG, "Network transmission " + (enable ? "enabled" : "disabled")); |
| | | Timber.d("Network transmission " + (enable ? "enabled" : "disabled")); |
| | | } |
| | | |
| | | /** |
| | |
| | | if (result == 0) { |
| | | // 成功,跳出循环 |
| | | if (attempt > 0) { |
| | | Log.d(TAG, "prepareCamera succeeded on attempt " + (attempt + 1)); |
| | | Timber.d("prepareCamera succeeded on attempt " + (attempt + 1)); |
| | | } |
| | | break; |
| | | } else { |
| | | // 失败,记录日志 |
| | | Log.w(TAG, "prepareCamera failed on attempt " + (attempt + 1) + ": " + result); |
| | | Timber.w( "prepareCamera failed on attempt " + (attempt + 1) + ": " + result); |
| | | if (attempt < maxRetries - 1) { |
| | | Log.d(TAG, "Retrying prepareCamera..."); |
| | | Timber.d( "Retrying prepareCamera..."); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (result != 0) { |
| | | Log.e(TAG, "prepareCamera failed after " + maxRetries + " attempts: " + result); |
| | | Timber.e("prepareCamera failed after " + maxRetries + " attempts: " + result); |
| | | return false; |
| | | } |
| | | |
| | | // 更新实际分辨率 |
| | | width = actualResolution[0]; |
| | | height = actualResolution[1]; |
| | | Log.d(TAG, "Camera initialized with resolution: " + width + "x" + height); |
| | | Timber.d("Camera initialized with resolution: " + width + "x" + height); |
| | | |
| | | // 3. 初始化H264编码器 |
| | | initEncoder(); |
| | |
| | | return false; |
| | | } |
| | | } else { |
| | | Log.d(TAG, "Network transmission disabled, skipping socket initialization"); |
| | | Timber.d("Network transmission disabled, skipping socket initialization"); |
| | | } |
| | | |
| | | // 5. 初始化文件输出(仅创建文件,SPS/PPS在第一次输出时写入) |
| | | if (enableFileOutput && outputFilePath != null && !outputFilePath.isEmpty()) { |
| | | if (!initFileOutput()) { |
| | | Log.w(TAG, "File output initialization failed, continuing without file output"); |
| | | Timber.w("File output initialization failed, continuing without file output"); |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize failed", e); |
| | | Timber.e(e,"Initialize failed"); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| | | encoder.start(); |
| | | |
| | | Log.d(TAG, "H264 encoder initialized"); |
| | | Timber.d( "H264 encoder initialized"); |
| | | } |
| | | |
| | | /** |
| | |
| | | if (parentDir != null && !parentDir.exists()) { |
| | | boolean created = parentDir.mkdirs(); |
| | | if (!created && !parentDir.exists()) { |
| | | Log.e(TAG, "Failed to create parent directory: " + parentDir.getAbsolutePath()); |
| | | Timber.e("Failed to create parent directory: " + parentDir.getAbsolutePath()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | fileOutputStream = new FileOutputStream(file); |
| | | spsPpsWritten = false; |
| | | |
| | | Log.d(TAG, "File output initialized: " + outputFilePath); |
| | | Timber.d("File output initialized: " + outputFilePath); |
| | | return true; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Initialize file output failed", e); |
| | | Timber.e(e,"Initialize file output failed"); |
| | | if (fileOutputStream != null) { |
| | | try { |
| | | fileOutputStream.close(); |
| | | } catch (IOException ie) { |
| | | Log.e(TAG, "Close file output stream failed", ie); |
| | | Timber.e(ie, "Close file output stream failed"); |
| | | } |
| | | fileOutputStream = null; |
| | | } |
| | |
| | | fileOutputStream.flush(); |
| | | |
| | | spsPpsWritten = true; |
| | | Log.d(TAG, "SPS/PPS written to file, SPS size: " + spsLength + ", PPS size: " + ppsLength); |
| | | Timber.d("SPS/PPS written to file, SPS size: " + spsLength + ", PPS size: " + ppsLength); |
| | | } else { |
| | | Log.w(TAG, "SPS/PPS not found in CSD, will extract from first key frame"); |
| | | Timber.w("SPS/PPS not found in CSD, will extract from first key frame"); |
| | | } |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Write SPS/PPS to file error", e); |
| | | Timber.e(e,"Write SPS/PPS to file error"); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | public void start() { |
| | | if (isRunning.get()) { |
| | | Log.w(TAG, "Encoder is already running"); |
| | | Timber.w("Encoder is already running"); |
| | | return; |
| | | } |
| | | |
| | |
| | | }); |
| | | encodeThread.start(); |
| | | |
| | | Log.d(TAG, "H264 encoder started"); |
| | | Timber.d("H264 encoder started"); |
| | | } |
| | | |
| | | /** |
| | |
| | | // processCamera - 读取一帧 |
| | | int processResult = usbCamera.processCamera(); |
| | | if (processResult != 0) { |
| | | Log.w(TAG, "processCamera returned: " + processResult); |
| | | Timber.w("processCamera returned: " + processResult); |
| | | Thread.sleep(10); |
| | | continue; |
| | | } |
| | |
| | | encodeFrame(nv12ToNV21(yuvBuffer,width,height), timestamp, bufferInfo); |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Encode loop error", e); |
| | | Timber.e(e, "Encode loop error"); |
| | | try { |
| | | Thread.sleep(100); |
| | | } catch (InterruptedException ie) { |
| | |
| | | } |
| | | } |
| | | |
| | | Log.d(TAG, "Encode loop exited"); |
| | | Timber.d( "Encode loop exited"); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Encode frame error", e); |
| | | Timber.e(e,"Encode frame error"); |
| | | } |
| | | } |
| | | |
| | |
| | | // MediaCodec输出的关键帧通常已经包含SPS/PPS,但为了确保文件完整性, |
| | | // 我们已经从CSD写入,这里直接写入关键帧数据即可 |
| | | if (!spsPpsWritten) { |
| | | Log.d(TAG, "SPS/PPS will be included in key frame data"); |
| | | Timber.d("SPS/PPS will be included in key frame data"); |
| | | } |
| | | } |
| | | |
| | |
| | | fileOutputStream.write(data); |
| | | fileOutputStream.flush(); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Write to file error", e); |
| | | Timber.e(e, "Write to file error"); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Send encoded data error", e); |
| | | Timber.e(e,"Send encoded data error"); |
| | | } |
| | | } |
| | | |
| | |
| | | try { |
| | | encodeThread.join(2000); |
| | | } catch (InterruptedException e) { |
| | | Log.e(TAG, "Wait encode thread error", e); |
| | | Timber.e(e, "Wait encode thread error"); |
| | | } |
| | | } |
| | | |
| | |
| | | encoder.release(); |
| | | encoder = null; |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "Release encoder error", e); |
| | | Timber.e(e, "Release encoder error"); |
| | | } |
| | | } |
| | | |
| | |
| | | // 关闭文件输出 |
| | | closeFileOutput(); |
| | | |
| | | Log.d(TAG, "H264 encoder stopped"); |
| | | Timber.d("H264 encoder stopped"); |
| | | } |
| | | |
| | | /** |
| | |
| | | try { |
| | | fileOutputStream.flush(); |
| | | fileOutputStream.close(); |
| | | Log.d(TAG, "File output closed: " + outputFilePath); |
| | | Timber.d("File output closed: " + outputFilePath); |
| | | } catch (IOException e) { |
| | | Log.e(TAG, "Close file output error", e); |
| | | Timber.e(e, "Close file output error"); |
| | | } finally { |
| | | fileOutputStream = null; |
| | | spsPpsWritten = false; |