ffmpeg_linux.c 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #include <assert.h>
  2. #include <stdio.h>
  3. #include <stdint.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <sys/types.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <raylib.h>
  11. #define READ_END 0
  12. #define WRITE_END 1
  13. typedef struct {
  14. int pipe;
  15. pid_t pid;
  16. } FFMPEG;
  17. FFMPEG *ffmpeg_start_rendering(size_t width, size_t height, size_t fps, const char *sound_file_path)
  18. {
  19. int pipefd[2];
  20. if (pipe(pipefd) < 0) {
  21. TraceLog(LOG_ERROR, "FFMPEG: Could not create a pipe: %s", strerror(errno));
  22. return NULL;
  23. }
  24. pid_t child = fork();
  25. if (child < 0) {
  26. TraceLog(LOG_ERROR, "FFMPEG: could not fork a child: %s", strerror(errno));
  27. return NULL;
  28. }
  29. if (child == 0) {
  30. if (dup2(pipefd[READ_END], STDIN_FILENO) < 0) {
  31. TraceLog(LOG_ERROR, "FFMPEG CHILD: could not reopen read end of pipe as stdin: %s", strerror(errno));
  32. exit(1);
  33. }
  34. close(pipefd[WRITE_END]);
  35. char resolution[64];
  36. snprintf(resolution, sizeof(resolution), "%zux%zu", width, height);
  37. char framerate[64];
  38. snprintf(framerate, sizeof(framerate), "%zu", fps);
  39. int ret = execlp("ffmpeg",
  40. "ffmpeg",
  41. "-loglevel", "verbose",
  42. "-y",
  43. "-f", "rawvideo",
  44. "-pix_fmt", "rgba",
  45. "-s", resolution,
  46. "-r", framerate,
  47. "-i", "-",
  48. "-i", sound_file_path,
  49. "-c:v", "libx264",
  50. "-vb", "2500k",
  51. "-c:a", "aac",
  52. "-ab", "200k",
  53. "-pix_fmt", "yuv420p",
  54. "output.mp4",
  55. NULL
  56. );
  57. if (ret < 0) {
  58. TraceLog(LOG_ERROR, "FFMPEG CHILD: could not run ffmpeg as a child process: %s", strerror(errno));
  59. exit(1);
  60. }
  61. assert(0 && "unreachable");
  62. exit(1);
  63. }
  64. if (close(pipefd[READ_END]) < 0) {
  65. TraceLog(LOG_WARNING, "FFMPEG: could not close read end of the pipe on the parent's end: %s", strerror(errno));
  66. }
  67. FFMPEG *ffmpeg = malloc(sizeof(FFMPEG));
  68. assert(ffmpeg != NULL && "Buy MORE RAM lol!!");
  69. ffmpeg->pid = child;
  70. ffmpeg->pipe = pipefd[WRITE_END];
  71. return ffmpeg;
  72. }
  73. bool ffmpeg_end_rendering(FFMPEG *ffmpeg)
  74. {
  75. int pipe = ffmpeg->pipe;
  76. pid_t pid = ffmpeg->pid;
  77. free(ffmpeg);
  78. if (close(pipe) < 0) {
  79. TraceLog(LOG_WARNING, "FFMPEG: could not close write end of the pipe on the parent's end: %s", strerror(errno));
  80. }
  81. for (;;) {
  82. int wstatus = 0;
  83. if (waitpid(pid, &wstatus, 0) < 0) {
  84. TraceLog(LOG_ERROR, "FFMPEG: could not wait for ffmpeg child process to finish: %s", strerror(errno));
  85. return false;
  86. }
  87. if (WIFEXITED(wstatus)) {
  88. int exit_status = WEXITSTATUS(wstatus);
  89. if (exit_status != 0) {
  90. TraceLog(LOG_ERROR, "FFMPEG: ffmpeg exited with code %d", exit_status);
  91. return false;
  92. }
  93. return true;
  94. }
  95. if (WIFSIGNALED(wstatus)) {
  96. TraceLog(LOG_ERROR, "FFMPEG: ffmpeg got terminated by %s", strsignal(WTERMSIG(wstatus)));
  97. return false;
  98. }
  99. }
  100. assert(0 && "unreachable");
  101. }
  102. bool ffmpeg_send_frame_flipped(FFMPEG *ffmpeg, void *data, size_t width, size_t height)
  103. {
  104. for (size_t y = height; y > 0; --y) {
  105. // TODO: write() may not necessarily write the entire row. We may want to repeat the call.
  106. if (write(ffmpeg->pipe, (uint32_t*)data + (y - 1)*width, sizeof(uint32_t)*width) < 0) {
  107. TraceLog(LOG_ERROR, "FFMPEG: failed to write into ffmpeg pipe: %s", strerror(errno));
  108. return false;
  109. }
  110. }
  111. return true;
  112. }