rtsp转rtmp音视频流只对音频重编码

亲问一下javacv如何实现 ffmpeg 命令 -vcodec copy -acodec aac 一样的效果?

recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.start();
recorder.record(frame);

这样写cpu占用太高了;
如果直接用转封装的方式,又不能对音频重编码,导致视频无法播放
输入流类型如下:

img

大部分回答音频编码确实ok, 但是视频编码会变成h263, 如下:

img

谢邀。直截了当的讲,到目前为止JavaCV是不支持类似ffmpeg那样一边同时视频转封装(转复用),一边同时音频转码的功能。您可以通过javacpp-ffmpeg方式调用底层api方式实现,类似c++代码中的那样,但是复杂度提高很多

该回答引用ChatGPT
根据问题描述,您想要实现将 RTSP 流转换为 RTMP 流,并且只对音频进行重编码,同时保持视频编码不变。您已经尝试使用 javacv 库来实现,但是发现 CPU 占用过高,同时使用转封装的方式又不能对音频进行重编码,导致视频无法播放。
针对这个问题,我建议您可以尝试使用 FFmpeg 库来实现。具体步骤如下:
1. 使用 FFmpeg 库打开 RTSP 流,并且设置输出格式为 RTMP:

AVFormatContext *inputFormatContext = NULL;
AVFormatContext *outputFormatContext = NULL;
AVOutputFormat *outputFormat = NULL;
AVStream *inputStream = NULL;
AVStream *outputStream = NULL;
// 打开 RTSP 流
avformat_open_input(&inputFormatContext, "rtsp://xxx.xxx.xxx.xxx:xxxx", NULL, NULL);
// 查找音视频流
avformat_find_stream_info(inputFormatContext, NULL);
// 设置输出格式为 RTMP
avformat_alloc_output_context2(&outputFormatContext, NULL, "flv", "rtmp://xxx.xxx.xxx.xxx:xxxx");
// 添加音视频流
for (int i = 0; i < inputFormatContext->nb_streams; i++) {
inputStream = inputFormatContext->streams[i];
outputStream = avformat_new_stream(outputFormatContext, inputStream->codec->codec);
avcodec_copy_context(outputStream->codec, inputStream->codec);
outputStream->codec->codec_tag = 0;
if (outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
outputStream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
}
// 打开输出流
avio_open(&outputFormatContext->pb, "rtmp://xxx.xxx.xxx.xxx:xxxx", AVIO_FLAG_WRITE);
avformat_write_header(outputFormatContext, NULL);

2. 循环读取 RTSP 流中的音视频帧,并且将音频帧重编码后写入 RTMP 流中:

AVPacket packet;
AVFrame *frame = av_frame_alloc();
AVCodecContext *audioCodecContext = NULL;
AVCodec *audioCodec = NULL;
int audioStreamIndex = -1;
// 查找音频流
for (int i = 0; i < inputFormatContext->nb_streams; i++) {
if (inputFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStreamIndex = i;
break;
}
}
// 打开音频编码器
audioCodecContext = inputFormatContext->streams[audioStreamIndex]->codec;
audioCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
avcodec_open2(audioCodecContext, audioCodec, NULL);
// 循环读取音视频帧
while (av_read_frame(inputFormatContext, &packet) >= 0) {
if (packet.stream_index == audioStreamIndex) {
// 对音频帧进行重编码
avcodec_send_packet(audioCodecContext, &packet);
while (avcodec_receive_frame(audioCodecContext, frame) == 0) {
frame->pts = av_rescale_q(frame->pts, audioCodecContext->time_base, outputStream->time_base);
frame->pkt_dts = frame->pts;
av_interleaved_write_frame(outputFormatContext, &packet);
av_frame_unref(frame);
}
} else {
// 直接写入视频帧
av_interleaved_write_frame(outputFormatContext, &packet);
}
av_packet_unref(&packet);
}
// 关闭输入输出流
av_write_trailer(outputFormatContext);
avformat_close_input(&inputFormatContext);
avio_close(outputFormatContext->pb);
avformat_free_context(outputFormatContext);

这样,您就可以实现将 RTSP 流转换为 RTMP 流,并且只对音频进行重编码,同时保持视频编码不变的效果了。

该回答引用GPT:要实现类似于FFmpeg命令“-vcodec copy -acodec aac”的效果,可以通过以下步骤使用JavaCV实现:

1 创建FFmpegFrameRecorder对象,并使用setFormat方法指定输出格式(例如:“flv”)。
2 使用setVideoCodec方法设置视频编码器为与输入流相同的编码器(grabber.getVideoCodec())。
3 使用setAudioCodec方法将音频编码器设置为AAC编码器(avcodec.AV_CODEC_ID_AAC)。
4 使用start方法启动recorder。
5 循环读取输入流,使用record方法将每个frame写入recorder。
6 使用stop方法停止recorder。
下面是示例代码:

FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("rtsp://example.com/stream");
grabber.start();

FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://example.com/live/stream", grabber.getImageWidth(), grabber.getImageHeight());
recorder.setFormat("flv");
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.start();

Frame frame = null;
while ((frame = grabber.grabFrame()) != null) {
    recorder.record(frame);
}

recorder.stop();
grabber.stop();


请注意,将视频编码器设置为“与输入流相同的编码器”可能会导致视频无法播放,因为某些播放器可能无法解码该编码器。如果需要更广泛的兼容性,请使用H.264或VP8等常用编码器。如果您仍然需要使用与输入流相同的编码器,则可以尝试将recorder.setInterleaved(true)设置为true,这将启用交错模式,以便在视频帧之间保留输入流中的原始帧。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
实现 ffmpeg 命令 -vcodec copy -acodec aac 一样的效果,可以使用 FFmpegFrameRecorder 类,并通过 FFmpeg 的编码器进行编码,具体如下:

FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputUrl, inputWidth, inputHeight);

//设置视频编码参数
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 视频编码
recorder.setFormat("flv"); // 封装格式,如果需要推送到rtmp则需要设置为flv
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // 像素格式

//设置音频编码参数
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 音频编码
recorder.setAudioChannels(2);
recorder.setSampleRate(44100);

recorder.start();
//如果想要不进行视频编码直接copy,可以使用如下方式
//grabber.setOption("vcodec", "copy");
Frame frame;
while ((frame = grabber.grab()) != null) {
  recorder.record(frame);
}
recorder.stop();

注意事项:

  • 在设置音视频编码的时候,需要根据自己的输入流的编码进行设置。
  • 在使用 FFmpegFrameRecorder 的时候,需要引入 javacv-platform 依赖。
  • 如果想要不进行视频编码直接 copy,则需要使用 grabber.setOption("vcodec", "copy") 来设置。
    如果我的回答解决了您的问题,请采纳!
可以尝试使用 javacv 提供的 FFmpegFrameRecorder 类来实现相应的功能,具体实现如下:

FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://xxx", grabber.getImageWidth(), grabber.getImageHeight(), 1);
recorder.setFrameRate(grabber.getFrameRate());
recorder.setFormat("flv");
recorder.setVideoOption("crf", "18");
recorder.setVideoOption("preset", "ultrafast");
recorder.setVideoCodecName("copy");
recorder.setAudioCodecName("aac");
recorder.setAudioOption("b", "128k");
recorder.setAudioQuality(0);
recorder.start();

Frame frame;
while ((frame = grabber.grab()) != null) {
    recorder.record(frame);
}

recorder.stop();
在这里,我们指定了 recorder.setVideoCodecName("copy"),表示将视频流进行转封装,不进行重编码。而对于音频,我们则指定了 recorder.setAudioCodecName("aac"),表示将音频流进行 AAC 编码。

需要注意的是,由于音频和视频的编码方式不同,所以在进行音视频合并的时候,可能会出现音视频不同步的情况。如果出现这种情况,可以尝试对音频流进行时间戳调整,或者对音频流进行降采样等操作,以达到音视频同步的效果。

FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("rtsp://your_stream_url");
grabber.start();

// 视频编码器
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://your_rtmp_url", grabber.getImageWidth(), grabber.getImageHeight(), 1);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat("flv");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(2000000);
recorder.setPixelFormat(grabber.getPixelFormat());
recorder.setVideoOption("tune", "zerolatency"); // 关闭编码器延迟优化
recorder.start();

// 音频编码器
FFmpegFrameRecorder audioRecorder = new FFmpegFrameRecorder("rtmp://your_rtmp_url", 0);
audioRecorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
audioRecorder.setAudioBitrate(96000);
audioRecorder.setAudioChannels(2);
audioRecorder.setSampleRate(44100);
audioRecorder.start();

Frame frame;
while ((frame = grabber.grab()) != null) {
    if (frame.image != null) {
        recorder.setTimestamp(grabber.getTimestamp());
        recorder.record(frame);
    }

    if (frame.samples != null) {
        audioRecorder.setTimestamp(grabber.getTimestamp());
        audioRecorder.record(frame);
    }
}

该回答通过自己思路及引用到GPTᴼᴾᴱᴺᴬᴵ搜索,得到内容具体如下:

可以使用FFmpegFrameRecorder类来实现 -vcodec copy -acodec aac 的效果,示例如下:

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;

FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, grabber.getImageWidth(), grabber.getImageHeight());
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoCodec("copy");
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setSampleFormat(grabber.getSampleFormat());
recorder.setSampleRate(grabber.getSampleRate());
recorder.start();

Frame frame;
while ((frame = grabber.grab()) != null) {
    recorder.record(frame);
}

recorder.stop();

其中,setVideoCodec("copy") 设置视频编码器为“拷贝”,即直接拷贝输入视频的编码格式;setAudioCodec(avcodec.AV_CODEC_ID_AAC) 设置音频编码器为AAC;其他的设置与输入流保持一致。


如果以上回答对您有所帮助,点击一下采纳该答案~谢谢

可以使用JavaCV的FFmpegFrameRecorder类来设置视频编码器和音频编码器,以实现类似于FFmpeg命令“-vcodec copy -acodec aac”的效果。

要设置视频编码器为“copy”,请将以下代码添加到您的记录器代码中:


recorder.setVideoCodec(avcodec.AV_CODEC_ID_COPY);

要设置音频编码器为“AAC”,请将以下代码添加到您的记录器代码中:


recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);

如果CPU使用率仍然很高,您可以尝试跳帧进行处理,这取决于您的应用程序的实现方式。另外,可能还需要调整视频帧大小和音频采样率等参数,以进一步优化性能和质量。

要实现与FFmpeg命令“-vcodec copy -acodec aac”类似的效果,需要将视频流直接复制到输出文件中,而同时重新编码音频流。可以使用FFmpeg提供的filtergraph实现该功能。以下是使用JavaCV实现该功能的代码示例:

FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("input.mp4");
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("output.mp4", grabber.getImageWidth(), grabber.getImageHeight());
recorder.setFrameRate(grabber.getFrameRate());
recorder.setFormat("mp4");

// 设置video filter
String videoFilter = "copy";

// 设置audio filter
String audioFilter = String.format("asetpts=PTS-STARTPTS,aresample=%d,aformat=channel_layouts=stereo,acodec=aac", grabber.getSampleRate());

recorder.setVideoOption("filter:v", videoFilter);
recorder.setAudioOption("filter:a", audioFilter);

recorder.start();
Frame frame;
while ((frame = grabber.grab()) != null) {
    recorder.record(frame);
}
recorder.stop();
recorder.release();
grabber.stop();
grabber.release();

在上述代码中,首先通过FFmpegFrameGrabber获取视频流,并使用FFmpegFrameRecorder创建一个输出文件,然后设置视频的帧率、格式等参数。接着,我们通过设置video filter为“copy”,实现视频流直接复制到输出文件中。而对于音频流,则需要重新编码,因此我们使用了filtergraph实现了这一点。在audio filter中,使用“aresample”重新采样音频流,然后使用“aformat”将采样率设置为stereo并设置音频编码格式为aac。最后,通过while循环,将每一帧的视频和音频写入输出文件中。