Android Studio JavaCV场景下ProcessBuilder.start()启动进程失败
问题根因
pb.start() 抛出空指针的直接原因是传入ProcessBuilder的命令数组中存在null元素,对应你代码里的问题就是ffmpeg可执行文件路径值为null。
你把Loader.load(org.bytedeco.ffmpeg.ffmpeg.class)写在了类的静态成员初始化位置:静态成员会在类加载阶段就执行初始化,这个时机App还没完成Context绑定,JavaCV的Loader必须依赖有效应用Context才能把内置的ffmpeg二进制解压到App私有目录、返回可执行文件路径,没有Context的情况下Loader加载失败直接返回null,最终传入命令数组触发空指针。
对照Android系统源码,ProcessBuilder类第1012行的逻辑就是遍历命令数组做非空校验,只要存在null元素就直接抛出NullPointerException,和你抓到的崩溃堆栈完全匹配。
修复方案
- 把ffmpeg路径加载、命令数组组装的逻辑从静态成员区挪到子线程的执行逻辑里,保证调用Loader.load时App已经完成初始化,能给Loader提供有效Context
- 输出文件不要直接写
test.flv,Android 10以上开启分区存储后,App对存储根目录没有写权限,要把输出路径指定到App私有目录下,否则就算进程启动成功也会因为写文件失败直接退出 - 删除重复的
pb.redirectErrorStream(true)调用,进程启动后要单独开线程消费进程的标准输出流,否则缓冲区写满后会直接卡死ffmpeg进程 - 删掉onCreate上无意义的
@RequiresApi(api = Build.VERSION_CODES.O)注解,ProcessBuilder从API 1就存在,不需要Android 8.0以上版本支持,加这个注解反而可能在低版本系统上触发不必要的类加载错误
修正后的核心代码
public class MainActivity extends AppCompatActivity { static final int cols = 192; static final int rows = 108; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(t1).start(); } private final Runnable t1 = () -> { Log.e("TAG", "工作线程启动"); // 在线程内加载ffmpeg,此时App已完成初始化,Loader可正常获取Context String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class); // 输出路径指定到App私有缓存目录,规避存储权限问题 String outputPath = new File(getCacheDir(), "test.flv").getAbsolutePath(); String[] command = {ffmpeg, "-y", "-f", "rawvideo", "-vcodec", "rawvideo", "-pix_fmt", "bgr24", "-s", cols + "x" + rows, "-r", "10", "-i", "pipe:", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "ultrafast", "-f", "flv", outputPath}; ProcessBuilder pb = new ProcessBuilder(command).redirectErrorStream(true); try { Process process = pb.start(); // 单独开线程读取ffmpeg输出日志,避免缓冲区阻塞 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { Log.d("FFMPEG_OUT", line); } } catch (IOException e) { e.printStackTrace(); } }).start(); OutputStream writer = process.getOutputStream(); Log.e("TAG", "ffmpeg进程启动成功"); // 后续在这里往writer写入raw视频帧数据,处理完后调用process.waitFor()获取退出码 } catch (Exception e) { e.printStackTrace(); Log.e("TAG", "进程启动失败", e); } }; }
额外提醒:如果后续要推流到RTMP服务器,记得给App加INTERNET权限,否则网络请求会被系统拦截。
内容的提问来源于stack exchange,提问作者xnok
相关产品推荐
相关产品推荐

