流媒体实现
parent
a38f473ce8
commit
ee68539dde
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,231 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/1.
|
||||
//
|
||||
|
||||
#include "RTSPRecorder.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <android/log.h>
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
|
||||
#define LOG_TAG "libcurl"
|
||||
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
|
||||
void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration)
|
||||
{
|
||||
AVFormatContext* inputFormatContext = nullptr;
|
||||
AVFormatContext* outputFormatContext = nullptr;
|
||||
AVPacket packet;
|
||||
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
|
||||
// Open input RTMP stream
|
||||
if (avformat_open_input(&inputFormatContext, rtmpUrl, nullptr, nullptr) != 0) {
|
||||
fprintf(stderr, "Could not open input file '%s'\n", rtmpUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve input stream information
|
||||
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
|
||||
fprintf(stderr, "Could not find stream information\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open output MP4 file
|
||||
if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mp4", outputPath) < 0) {
|
||||
fprintf(stderr, "Could not create output context\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy stream information from input to output
|
||||
for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) {
|
||||
AVStream* inStream = inputFormatContext->streams[i];
|
||||
AVStream* outStream = avformat_new_stream(outputFormatContext, nullptr);
|
||||
if (!outStream) {
|
||||
fprintf(stderr, "Failed to allocate output stream\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avcodec_parameters_copy(outStream->codecpar, inStream->codecpar) < 0) {
|
||||
fprintf(stderr, "Failed to copy codec parameters\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
outStream->codecpar->codec_tag = 0;
|
||||
}
|
||||
|
||||
// Open output file
|
||||
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
||||
if (avio_open(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE) < 0) {
|
||||
fprintf(stderr, "Could not open output file '%s'\n", outputPath);
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Write output file header
|
||||
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
|
||||
fprintf(stderr, "Error occurred when writing header to output file\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a thread to stop the streaming after the specified duration
|
||||
std::thread stop_thread([&]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||
av_read_pause(inputFormatContext);
|
||||
});
|
||||
|
||||
// Read packets from input and write them to output
|
||||
while (av_read_frame(inputFormatContext, &packet) >= 0) {
|
||||
AVStream* inStream = inputFormatContext->streams[packet.stream_index];
|
||||
AVStream* outStream = outputFormatContext->streams[packet.stream_index];
|
||||
|
||||
packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);
|
||||
packet.pos = -1;
|
||||
|
||||
if (av_interleaved_write_frame(outputFormatContext, &packet) < 0) {
|
||||
fprintf(stderr, "Error muxing packet\n");
|
||||
break;
|
||||
}
|
||||
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
|
||||
stop_thread.join();
|
||||
|
||||
// Write output file trailer
|
||||
av_write_trailer(outputFormatContext);
|
||||
|
||||
// Clean up
|
||||
avformat_close_input(&inputFormatContext);
|
||||
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
||||
avio_closep(&outputFormatContext->pb);
|
||||
}
|
||||
avformat_free_context(outputFormatContext);
|
||||
}
|
||||
|
||||
|
||||
void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration)
|
||||
{
|
||||
AVFormatContext* inputFormatContext = nullptr;
|
||||
AVFormatContext* outputFormatContext = nullptr;
|
||||
AVPacket packet;
|
||||
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
|
||||
// Open input RTSP stream
|
||||
if (avformat_open_input(&inputFormatContext, rtspUrl, nullptr, nullptr) != 0) {
|
||||
fprintf(stderr, "Could not open input file '%s'\n", rtspUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve input stream information
|
||||
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
|
||||
fprintf(stderr, "Could not find stream information\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open output MP4 file
|
||||
if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mp4", outputPath) < 0) {
|
||||
fprintf(stderr, "Could not create output context\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy stream information from input to output
|
||||
for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) {
|
||||
AVStream* inStream = inputFormatContext->streams[i];
|
||||
AVStream* outStream = avformat_new_stream(outputFormatContext, nullptr);
|
||||
if (!outStream) {
|
||||
fprintf(stderr, "Failed to allocate output stream\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avcodec_parameters_copy(outStream->codecpar, inStream->codecpar) < 0) {
|
||||
fprintf(stderr, "Failed to copy codec parameters\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
outStream->codecpar->codec_tag = 0;
|
||||
}
|
||||
|
||||
// Open output file
|
||||
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
||||
if (avio_open(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE) < 0) {
|
||||
fprintf(stderr, "Could not open output file '%s'\n", outputPath);
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Write output file header
|
||||
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
|
||||
fprintf(stderr, "Error occurred when writing header to output file\n");
|
||||
avformat_close_input(&inputFormatContext);
|
||||
avformat_free_context(outputFormatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a thread to stop the streaming after the specified duration
|
||||
std::thread stop_thread([&]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||
av_read_pause(inputFormatContext);
|
||||
});
|
||||
|
||||
// Read packets from input and write them to output
|
||||
while (av_read_frame(inputFormatContext, &packet) >= 0) {
|
||||
AVStream* inStream = inputFormatContext->streams[packet.stream_index];
|
||||
AVStream* outStream = outputFormatContext->streams[packet.stream_index];
|
||||
|
||||
packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);
|
||||
packet.pos = -1;
|
||||
|
||||
if (av_interleaved_write_frame(outputFormatContext, &packet) < 0) {
|
||||
fprintf(stderr, "Error muxing packet\n");
|
||||
break;
|
||||
}
|
||||
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
|
||||
stop_thread.join();
|
||||
|
||||
// Write output file trailer
|
||||
av_write_trailer(outputFormatContext);
|
||||
|
||||
// Clean up
|
||||
avformat_close_input(&inputFormatContext);
|
||||
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
||||
avio_closep(&outputFormatContext->pb);
|
||||
}
|
||||
avformat_free_context(outputFormatContext);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Matthew on 2025/3/1.
|
||||
//
|
||||
|
||||
#ifndef MICROPHOTO_RTSPRECORDER_H
|
||||
#define MICROPHOTO_RTSPRECORDER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// void dumpRtspToMp4(const std::string &rtspUrl, const std::string &outputPath, uint32_t durationInMs);
|
||||
void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration);
|
||||
void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration);
|
||||
|
||||
class RTSPRecorder {
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //MICROPHOTO_RTSPRECORDER_H
|
@ -0,0 +1,186 @@
|
||||
//
|
||||
// Created by Matthew on 2025/2/28.
|
||||
//
|
||||
|
||||
#include "RTSPToMP4.h"
|
||||
#include <android/native_window.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
int32_t getMaxInputSize(AMediaExtractor* extractor, size_t trackIndex)
|
||||
{
|
||||
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackIndex);
|
||||
int32_t maxInputSize = 0;
|
||||
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, &maxInputSize)) {
|
||||
// LOGI("Max input size for track %zu: %d", trackIndex, maxInputSize);
|
||||
} else {
|
||||
// LOGE("Failed to get max input size for track %zu", trackIndex);
|
||||
}
|
||||
AMediaFormat_delete(format);
|
||||
return maxInputSize;
|
||||
}
|
||||
|
||||
RTSPToMP4::RTSPToMP4(const char* rtspUrl, const char* outputPath, uint64_t durationInMs/* = 0*/)
|
||||
: fd(-1), codec(nullptr), extractor(nullptr), muxer(nullptr), videoTrackIndex(-1), durationInMs(durationInMs), running(false) {
|
||||
initExtractor(rtspUrl);
|
||||
initCodec("video/avc");
|
||||
initMuxer(outputPath);
|
||||
}
|
||||
|
||||
RTSPToMP4::~RTSPToMP4() {
|
||||
if (codec) AMediaCodec_delete(codec);
|
||||
if (extractor) AMediaExtractor_delete(extractor);
|
||||
if (muxer) AMediaMuxer_delete(muxer);
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
fdatasync(fd);
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void RTSPToMP4::initCodec(const char* mime) {
|
||||
codec = AMediaCodec_createDecoderByType(mime);
|
||||
AMediaFormat* format = AMediaFormat_new();
|
||||
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime);
|
||||
// Set other format parameters as needed
|
||||
// ...
|
||||
AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
|
||||
AMediaFormat_delete(format);
|
||||
}
|
||||
|
||||
void RTSPToMP4::initExtractor(const char* rtspUrl) {
|
||||
extractor = AMediaExtractor_new();
|
||||
media_status_t status = AMediaExtractor_setDataSource(extractor, rtspUrl);
|
||||
if (status != AMEDIA_OK) {
|
||||
// Handle error
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void RTSPToMP4::initMuxer(const char* outputPath) {
|
||||
fd = open(outputPath, O_CREAT | O_WRONLY, 0644);
|
||||
muxer = AMediaMuxer_new(fd, AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
|
||||
|
||||
int numTracks = AMediaExtractor_getTrackCount(extractor);
|
||||
if (numTracks <= 0) {
|
||||
// LOGE("No tracks found in RTSP stream");
|
||||
AMediaExtractor_delete(extractor);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numTracks; ++i) {
|
||||
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, i);
|
||||
const char* mime;
|
||||
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime) && strncmp(mime, "video/", 6) == 0) {
|
||||
videoTrackIndex = AMediaMuxer_addTrack(muxer, format);
|
||||
AMediaExtractor_selectTrack(extractor, i);
|
||||
}
|
||||
AMediaFormat_delete(format);
|
||||
}
|
||||
|
||||
if (videoTrackIndex == -1) {
|
||||
// LOGE("No video track found in RTSP stream");
|
||||
AMediaExtractor_delete(extractor);
|
||||
AMediaMuxer_delete(muxer);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t maxInputSize = getMaxInputSize(extractor, videoTrackIndex);
|
||||
if (maxInputSize <= 0) {
|
||||
// LOGE("Invalid max input size");
|
||||
// releaseMediaExtractor(extractor);
|
||||
sampleData.resize(1920 * 1080 * 4, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
sampleData.resize(maxInputSize, 0);
|
||||
}
|
||||
|
||||
void RTSPToMP4::startDecodingAndMuxing() {
|
||||
AMediaCodec_start(codec);
|
||||
size_t bufferSize = sampleData.size();
|
||||
uint8_t* buffer = &sampleData[0];
|
||||
int64_t sampleTime = 0;
|
||||
int64_t startTime = 0;
|
||||
bool firstSampleData = true;
|
||||
|
||||
int64_t durationTime = (durationInMs == 0) ? std::numeric_limits<int64_t>::max() : (int64_t)durationInMs * 1000;
|
||||
|
||||
|
||||
while (running) {
|
||||
// Extract data from RTSP stream
|
||||
ssize_t sampleSize = AMediaExtractor_readSampleData(extractor, buffer, bufferSize);
|
||||
if (sampleSize < 0) {
|
||||
break; // End of stream
|
||||
}
|
||||
|
||||
sampleTime = AMediaExtractor_getSampleTime(extractor);
|
||||
if (firstSampleData)
|
||||
{
|
||||
startTime = sampleTime;
|
||||
firstSampleData = false;
|
||||
}
|
||||
|
||||
sampleTime -= startTime;
|
||||
|
||||
// Feed data to codec
|
||||
size_t inputBufferIndex;
|
||||
uint8_t* inputBuffer = AMediaCodec_getInputBuffer(codec, inputBufferIndex, &bufferSize);
|
||||
memcpy(inputBuffer, buffer, sampleSize);
|
||||
AMediaCodec_queueInputBuffer(codec, inputBufferIndex, 0, sampleSize, sampleTime, 0);
|
||||
|
||||
// Retrieve decoded frames and write to muxer
|
||||
AMediaCodecBufferInfo bufferInfo;
|
||||
ssize_t outputBufferIndex = AMediaCodec_dequeueOutputBuffer(codec, &bufferInfo, 0);
|
||||
if (outputBufferIndex >= 0) {
|
||||
|
||||
bufferInfo.offset = 0;
|
||||
bufferInfo.size = sampleSize;
|
||||
bufferInfo.presentationTimeUs = sampleTime;
|
||||
bufferInfo.flags = AMediaExtractor_getSampleFlags(extractor);
|
||||
|
||||
uint8_t* outputBuffer = AMediaCodec_getOutputBuffer(codec, outputBufferIndex, &bufferSize);
|
||||
AMediaMuxer_writeSampleData(muxer, videoTrackIndex, outputBuffer, &bufferInfo);
|
||||
AMediaCodec_releaseOutputBuffer(codec, outputBufferIndex, false);
|
||||
}
|
||||
|
||||
AMediaExtractor_advance(extractor);
|
||||
|
||||
if (sampleTime > durationTime)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AMediaCodec_stop(codec);
|
||||
AMediaMuxer_stop(muxer);
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
fdatasync(fd);
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void RTSPToMP4::start() {
|
||||
// Add video track to muxer
|
||||
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, 0);
|
||||
videoTrackIndex = AMediaMuxer_addTrack(muxer, format);
|
||||
running = true;
|
||||
AMediaMuxer_start(muxer);
|
||||
|
||||
startDecodingAndMuxing();
|
||||
}
|
||||
|
||||
void RTSPToMP4::stop() {
|
||||
running = false;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Created by Matthew on 2025/2/28.
|
||||
//
|
||||
|
||||
#ifndef MICROPHOTO_RTSPTOMP4_H
|
||||
#define MICROPHOTO_RTSPTOMP4_H
|
||||
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaExtractor.h>
|
||||
#include <media/NdkMediaMuxer.h>
|
||||
#include <vector>
|
||||
|
||||
class RTSPToMP4 {
|
||||
public:
|
||||
RTSPToMP4(const char* rtspUrl, const char* outputPath, uint64_t durationInMs = 0);
|
||||
~RTSPToMP4();
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void initCodec(const char* mime);
|
||||
void initExtractor(const char* rtspUrl);
|
||||
void initMuxer(const char* outputPath);
|
||||
void startDecodingAndMuxing();
|
||||
|
||||
int fd;
|
||||
AMediaCodec* codec;
|
||||
AMediaExtractor* extractor;
|
||||
AMediaMuxer* muxer;
|
||||
int videoTrackIndex;
|
||||
uint64_t durationInMs;
|
||||
bool running;
|
||||
|
||||
std::vector<uint8_t> sampleData;
|
||||
};
|
||||
|
||||
|
||||
#endif //MICROPHOTO_RTSPTOMP4_H
|
Loading…
Reference in New Issue