Skip to content

split_video_to_hls 支持其他语言 ​

native_media.h

c
#ifndef NATIVE_MEDIA_H
#define NATIVE_MEDIA_H

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Splits the specified video file into HLS segments.
 *
 * This function uses FFmpeg to convert a video file (e.g., MP4, MKV, MOV, AVI, etc.) into HLS segments.
 * The generated TS segment files are stored in the same directory as the HLS playlist file.
 * The tsPattern parameter defines the naming template for the TS segments (e.g., "segment_%03d.ts").
 *
 * @param playlistUrl     Full path to the HLS playlist file where the HLS segments will be appended.
 * @param inputVideoPath  Full path to the input video file.
 * @param tsPattern       Naming template (including directory) for TS segment files.
 * @param segmentDuration Duration of each TS segment in seconds.
 *
 * @return A constant char pointer to a message indicating the result of HLS segmentation,
 *         e.g., a success message or an error description.
 */
const char *split_video_to_hls(const char *playlistUrl,
                               const char *inputVideoPath,
                               const char *tsPattern,
                               int segmentDuration);

#ifdef __cplusplus
}
#endif

#endif // NATIVE_MEDIA_H

pure_video_to_hls.c

c
// pure_mp4_to_hls.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include "native_media.h"

#ifdef _WIN32

#include <windows.h>

#endif

// 辅助函数:打印 FFmpeg 错误信息
static void print_error(const char *msg, int errnum) {
  char errbuf[128] = {0};
  av_strerror(errnum, errbuf, sizeof(errbuf));
  fprintf(stderr, "%s: %s\n", msg, errbuf);
}

/**
 * 纯 C 接口:将 MP4 文件转换为 HLS 分段
 *
 * @param playlistUrl     播放列表文件路径(或 URL)
 * @param inputMp4Path    输入 MP4 文件路径
 * @param tsPattern       TS 分段文件命名模板(含完整路径),例如 "segment_%03d.ts"
 * @param segmentDuration TS 分段的时长(秒)
 * @param sceneIndex      分段起始编号
 *
 * @return 返回处理结果的描述字符串
 */
const char *split_video_to_hls(const char *playlistUrl, const char *inputMp4Path,
                               const char *tsPattern, int segmentDuration) {
  int ret = 0;
  AVFormatContext *ifmt_ctx = NULL;
  AVFormatContext *ofmt_ctx = NULL;
  AVDictionary *opts = NULL;
  int *stream_mapping = NULL;
  int stream_mapping_size = 0;
  AVPacket pkt;

  // 检查播放列表文件是否已经含有 "#EXT-X-ENDLIST"
  {
    FILE *checkFile = NULL;
#ifdef _WIN32
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, playlistUrl, -1, NULL, 0);
    wchar_t *wPlaylistUrl = (wchar_t *) malloc(size_needed * sizeof(wchar_t));
    if (wPlaylistUrl) {
      MultiByteToWideChar(CP_UTF8, 0, playlistUrl, -1, wPlaylistUrl, size_needed);
      checkFile = _wfopen(wPlaylistUrl, L"r");
      free(wPlaylistUrl);
    }
#else
    checkFile = fopen(playlistUrl, "r");
#endif
    if (checkFile) {
      fseek(checkFile, 0, SEEK_END);
      long size = ftell(checkFile);
      fseek(checkFile, 0, SEEK_SET);
      char *content = (char *) malloc(size + 1);
      if (content) {
        fread(content, 1, size, checkFile);
        content[size] = '\0';
        if (strstr(content, "#EXT-X-ENDLIST") != NULL) {
          fclose(checkFile);
          free(content);
          return "Playlist already contains #EXT-X-ENDLIST, cannot append";
        }
        free(content);
      }
      fclose(checkFile);
    }
  }

  // 打开输入 MP4 文件并获取流信息
  if ((ret = avformat_open_input(&ifmt_ctx, inputMp4Path, NULL, NULL)) < 0) {
    print_error("Unable to open input file", ret);
    goto end;
  }
  if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
    print_error("Unable to retrieve stream info", ret);
    goto end;
  }

  // 分配输出上下文,使用 "hls" Muxer,输出目标为 playlistUrl
  ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, "hls", playlistUrl);
  if (ret < 0 || !ofmt_ctx) {
    print_error("Unable to allocate output context", ret);
    goto end;
  }

  // 设置 HLS 选项:分段时长、起始编号、分段文件命名模板、追加模式、不限制列表长度、事件类型
  {
    char seg_time_str[16] = {0};
    snprintf(seg_time_str, sizeof(seg_time_str), "%d", segmentDuration);
    av_dict_set(&opts, "hls_time", seg_time_str, 0);

    av_dict_set(&opts, "hls_segment_filename", tsPattern, 0);
    av_dict_set(&opts, "hls_flags", "append_list", 0);
    av_dict_set(&opts, "hls_list_size", "0", 0);
    av_dict_set(&opts, "hls_playlist_type", "event", 0);
  }

  // 为每个有效输入流(视频、音频、字幕)创建对应的输出流
  stream_mapping_size = ifmt_ctx->nb_streams;
  stream_mapping = av_malloc_array(stream_mapping_size, sizeof(int));
  if (!stream_mapping) {
    ret = AVERROR(ENOMEM);
    goto end;
  }
  for (int i = 0, j = 0; i < ifmt_ctx->nb_streams; i++) {
    AVStream *in_stream = ifmt_ctx->streams[i];
    AVCodecParameters *in_codecpar = in_stream->codecpar;
    if (in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
        in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
        in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
      stream_mapping[i] = -1;
      continue;
    }
    stream_mapping[i] = j++;
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
      ret = AVERROR_UNKNOWN;
      goto end;
    }
    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
      print_error("Failed to copy codec parameters", ret);
      goto end;
    }
    out_stream->codecpar->codec_tag = 0;
    out_stream->time_base = in_stream->time_base;
  }

  // 打开输出文件(如果 Muxer 要求打开文件)
  if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
#ifdef _WIN32
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, playlistUrl, -1, NULL, 0);
    wchar_t *wPlaylistUrl = (wchar_t *) malloc(size_needed * sizeof(wchar_t));
    if (wPlaylistUrl) {
      MultiByteToWideChar(CP_UTF8, 0, playlistUrl, -1, wPlaylistUrl, size_needed);
      ret = avio_open(&ofmt_ctx->pb, wPlaylistUrl, AVIO_FLAG_WRITE);
      free(wPlaylistUrl);
    }
#else
    ret = avio_open(&ofmt_ctx->pb, playlistUrl, AVIO_FLAG_WRITE);
#endif
    if (ret < 0) {
      print_error("Unable to open output file", ret);
      goto end;
    }
  }

  // 写入 Muxer 文件头,启动生成 TS 分段及播放列表更新
  ret = avformat_write_header(ofmt_ctx, &opts);
  if (ret < 0) {
    print_error("Failed to write header", ret);
    goto end;
  }

  // 读取输入数据包,将时间戳转换后写入输出
  while (av_read_frame(ifmt_ctx, &pkt) >= 0) {
    if (pkt.stream_index >= ifmt_ctx->nb_streams ||
        stream_mapping[pkt.stream_index] < 0) {
      av_packet_unref(&pkt);
      continue;
    }
    AVStream *in_stream = ifmt_ctx->streams[pkt.stream_index];
    AVStream *out_stream = ofmt_ctx->streams[stream_mapping[pkt.stream_index]];

    pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                               AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
    pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                               AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
    pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
    pkt.pos = -1;
    pkt.stream_index = stream_mapping[pkt.stream_index];

    ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
    if (ret < 0) {
      print_error("Failed to write packet", ret);
      break;
    }
    av_packet_unref(&pkt);
  }

  // 结束时调用 trailer(本例中依然调用 trailer 以更新播放列表,如果需要避免写入 #EXT-X-ENDLIST 可注释)
  av_write_trailer(ofmt_ctx);

  end:
  if (stream_mapping)
    av_freep(&stream_mapping);
  if (ifmt_ctx)
    avformat_close_input(&ifmt_ctx);
  if (ofmt_ctx) {
    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
      avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
  }
  av_dict_free(&opts);

  if (ret < 0) {
    return "HLS segmentation failed";
  } else {
    static char resultMsg[256];
    snprintf(resultMsg, sizeof(resultMsg),
             "HLS segmentation successful: generated new segments and appended to playlist %s", playlistUrl);
    return resultMsg;
  }
}

修改 native_video_to_hls.c

java
#include "com_litongjava_media_NativeMedia.h"
#include <jni.h>
#include <stdlib.h>
#include "native_media.h"

// 声明纯 C 接口函数(如果 pure_mp4_to_hls.c 已经编译为库,可以通过头文件包含)
const char *split_mp4_to_hls(const char *playlistUrl, const char *inputMp4Path,
                             const char *tsPattern, int segmentDuration, int sceneIndex);

JNIEXPORT jstring JNICALL Java_com_litongjava_media_NativeMedia_splitVideoToHLS(
  JNIEnv *env, jclass clazz,
  jstring playlistUrlJ,
  jstring inputMp4PathJ,
  jstring tsPatternJ,
  jint segmentDuration) {

  // 从 Java 层字符串转换为 C 字符串
  const char *playlistUrl = (*env)->GetStringUTFChars(env, playlistUrlJ, NULL);
  const char *inputMp4Path = (*env)->GetStringUTFChars(env, inputMp4PathJ, NULL);
  const char *tsPattern = (*env)->GetStringUTFChars(env, tsPatternJ, NULL);

  // 调用纯 C 接口函数进行视频处理
  const char *result = split_video_to_hls(playlistUrl, inputMp4Path, tsPattern, segmentDuration);

  // 释放 JNI 字符串资源
  (*env)->ReleaseStringUTFChars(env, playlistUrlJ, playlistUrl);
  (*env)->ReleaseStringUTFChars(env, inputMp4PathJ, inputMp4Path);
  (*env)->ReleaseStringUTFChars(env, tsPatternJ, tsPattern);

  // 将结果转换为 jstring 返回给 Java
  return (*env)->NewStringUTF(env, result);
}
go
package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lmp4tohls -lavformat -lavutil -lavcodec -lm
#include <stdlib.h>
const char* split_video_to_hls(const char *playlistUrl,
                             const char *inputMp4Path,
                             const char *tsPattern,
                             int segmentDuration);
*/
import "C"

import (
  "fmt"
  "unsafe"
)

func main() {
  // 设置参数
  playlist := C.CString("/path/to/playlist.m3u8")
  inputMP4 := C.CString("/path/to/input.mp4")
  tsPattern := C.CString("/path/to/segment_%03d.ts")
  segmentDuration := C.int(10)

  // 调用 C 函数
  result := C.split_video_to_hls(playlist, inputMP4, tsPattern, segmentDuration)

  // 需要将 C 返回的字符串转换为 Go 字符串
  fmt.Println(C.GoString(result))

  // 释放 C 字符串(如果分配了内存)
  C.free(unsafe.Pointer(playlist))
  C.free(unsafe.Pointer(inputMP4))
  C.free(unsafe.Pointer(tsPattern))
}

MIT Licensed