mirror of
https://github.com/horgh/videostreamer.git
synced 2025-09-27 04:45:51 +08:00
Open input/output separately, and read/write separately
This is a refactor of the library API so that we will be able to have multiple outputs.
This commit is contained in:
@@ -21,10 +21,18 @@ int main(const int argc, const char * const * const argv)
|
||||
const char * const output_url = "file:/tmp/out.mp4";
|
||||
const bool verbose = true;
|
||||
|
||||
struct Videostreamer * const vs = vs_open(input_format, input_url,
|
||||
output_format, output_url, verbose);
|
||||
if (!vs) {
|
||||
printf("unable to open videostreamer\n");
|
||||
struct VSInput * const input = vs_open_input(input_format, input_url,
|
||||
verbose);
|
||||
if (!input) {
|
||||
printf("unable to open input\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct VSOutput * const output = vs_open_output(output_format, output_url,
|
||||
input, verbose);
|
||||
if (!output) {
|
||||
printf("unable to open output\n");
|
||||
vs_destroy_input(input);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -32,14 +40,31 @@ int main(const int argc, const char * const * const argv)
|
||||
int i = 0;
|
||||
|
||||
while (1) {
|
||||
const int frame_size = vs_read_write(vs, verbose);
|
||||
if (frame_size == -1) {
|
||||
printf("read/write failed\n");
|
||||
vs_destroy(vs);
|
||||
AVPacket pkt;
|
||||
memset(&pkt, 0, sizeof(AVPacket));
|
||||
|
||||
const int read_res = vs_read_packet(input, &pkt, verbose);
|
||||
if (read_res == -1) {
|
||||
printf("read failed\n");
|
||||
vs_destroy_input(input);
|
||||
vs_destroy_output(output);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("frame size %d\n", frame_size);
|
||||
if (read_res == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int write_res = vs_write_packet(input, output, &pkt, verbose);
|
||||
if (write_res == -1) {
|
||||
printf("write failed\n");
|
||||
vs_destroy_input(input);
|
||||
vs_destroy_output(output);
|
||||
av_packet_unref(&pkt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
i++;
|
||||
if (i == max_frames) {
|
||||
@@ -47,7 +72,8 @@ int main(const int argc, const char * const * const argv)
|
||||
}
|
||||
}
|
||||
|
||||
vs_destroy(vs);
|
||||
vs_destroy_input(input);
|
||||
vs_destroy_output(output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
260
videostreamer.c
260
videostreamer.c
@@ -34,77 +34,55 @@ vs_setup(void)
|
||||
avformat_network_init();
|
||||
}
|
||||
|
||||
struct Videostreamer *
|
||||
vs_open(const char * const input_format_name, const char * const input_url,
|
||||
const char * const output_format_name, const char * const output_url,
|
||||
const bool verbose)
|
||||
struct VSInput *
|
||||
vs_open_input(const char * const input_format_name,
|
||||
const char * const input_url, const bool verbose)
|
||||
{
|
||||
if (!input_format_name || strlen(input_format_name) == 0 ||
|
||||
!input_url || strlen(input_url) == 0 ||
|
||||
!output_format_name || strlen(output_format_name) == 0 ||
|
||||
!output_url || strlen(output_url) == 0) {
|
||||
!input_url || strlen(input_url) == 0) {
|
||||
printf("%s\n", strerror(EINVAL));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct Videostreamer * const vs = calloc(1, sizeof(struct Videostreamer));
|
||||
if (!vs) {
|
||||
struct VSInput * const input = calloc(1, sizeof(struct VSInput));
|
||||
if (!input) {
|
||||
printf("%s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Open input.
|
||||
|
||||
AVInputFormat * const input_format = av_find_input_format(input_format_name);
|
||||
if (!input_format) {
|
||||
printf("input format not found\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_input(input);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (avformat_open_input(&vs->input_format_ctx, input_url, input_format,
|
||||
if (avformat_open_input(&input->format_ctx, input_url, input_format,
|
||||
NULL) != 0) {
|
||||
printf("unable to open input\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_input(input);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(vs->input_format_ctx, NULL) < 0) {
|
||||
if (avformat_find_stream_info(input->format_ctx, NULL) < 0) {
|
||||
printf("failed to find stream info\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_input(input);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
if (verbose) {
|
||||
av_dump_format(vs->input_format_ctx, 0, input_url, 0);
|
||||
av_dump_format(input->format_ctx, 0, input_url, 0);
|
||||
}
|
||||
|
||||
|
||||
// Open output.
|
||||
// Find the first video stream.
|
||||
|
||||
AVOutputFormat * const output_format = av_guess_format(output_format_name,
|
||||
NULL, NULL);
|
||||
if (!output_format) {
|
||||
printf("output format not found\n");
|
||||
vs_destroy(vs);
|
||||
return NULL;
|
||||
}
|
||||
input->video_stream_index = -1;
|
||||
|
||||
if (avformat_alloc_output_context2(&vs->output_format_ctx, output_format,
|
||||
NULL, NULL) < 0) {
|
||||
printf("unable to create output context\n");
|
||||
vs_destroy(vs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Copy the first video stream.
|
||||
|
||||
vs->video_stream_index = -1;
|
||||
|
||||
for (unsigned int i = 0; i < vs->input_format_ctx->nb_streams; i++) {
|
||||
AVStream * const in_stream = vs->input_format_ctx->streams[i];
|
||||
for (unsigned int i = 0; i < input->format_ctx->nb_streams; i++) {
|
||||
AVStream * const in_stream = input->format_ctx->streams[i];
|
||||
|
||||
if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
|
||||
if (verbose) {
|
||||
@@ -113,41 +91,100 @@ vs_open(const char * const input_format_name, const char * const input_url,
|
||||
continue;
|
||||
}
|
||||
|
||||
AVStream * const out_stream = avformat_new_stream(vs->output_format_ctx,
|
||||
NULL);
|
||||
if (!out_stream) {
|
||||
printf("unable to add stream\n");
|
||||
vs_destroy(vs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (avcodec_parameters_copy(out_stream->codecpar,
|
||||
in_stream->codecpar) < 0) {
|
||||
printf("unable to copy codec parameters\n");
|
||||
vs_destroy(vs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// I take the first video stream only.
|
||||
vs->video_stream_index = (int) i;
|
||||
input->video_stream_index = (int) i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (vs->video_stream_index == -1) {
|
||||
if (input->video_stream_index == -1) {
|
||||
printf("no video stream found\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_input(input);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
void
|
||||
vs_destroy_input(struct VSInput * const input)
|
||||
{
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input->format_ctx) {
|
||||
avformat_close_input(&input->format_ctx);
|
||||
avformat_free_context(input->format_ctx);
|
||||
}
|
||||
|
||||
free(input);
|
||||
}
|
||||
|
||||
struct VSOutput *
|
||||
vs_open_output(const char * const output_format_name,
|
||||
const char * const output_url, const struct VSInput * const input,
|
||||
const bool verbose)
|
||||
{
|
||||
if (!output_format_name || strlen(output_format_name) == 0 ||
|
||||
!output_url || strlen(output_url) == 0 ||
|
||||
!input) {
|
||||
printf("%s\n", strerror(EINVAL));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct VSOutput * const output = calloc(1, sizeof(struct VSOutput));
|
||||
if (!output) {
|
||||
printf("%s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
AVOutputFormat * const output_format = av_guess_format(output_format_name,
|
||||
NULL, NULL);
|
||||
if (!output_format) {
|
||||
printf("output format not found\n");
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (avformat_alloc_output_context2(&output->format_ctx, output_format,
|
||||
NULL, NULL) < 0) {
|
||||
printf("unable to create output context\n");
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Copy the video stream.
|
||||
|
||||
AVStream * const out_stream = avformat_new_stream(output->format_ctx, NULL);
|
||||
if (!out_stream) {
|
||||
printf("unable to add stream\n");
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVStream * const in_stream = input->format_ctx->streams[
|
||||
input->video_stream_index];
|
||||
|
||||
if (avcodec_parameters_copy(out_stream->codecpar,
|
||||
in_stream->codecpar) < 0) {
|
||||
printf("unable to copy codec parameters\n");
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
if (verbose) {
|
||||
av_dump_format(vs->output_format_ctx, 0, output_url, 1);
|
||||
av_dump_format(output->format_ctx, 0, output_url, 1);
|
||||
}
|
||||
|
||||
|
||||
// Open output file.
|
||||
if (avio_open(&vs->output_format_ctx->pb, output_url, AVIO_FLAG_WRITE) < 0) {
|
||||
if (avio_open(&output->format_ctx->pb, output_url, AVIO_FLAG_WRITE) < 0) {
|
||||
printf("unable to open output file\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -172,20 +209,20 @@ vs_open(const char * const input_format_name, const char * const input_url,
|
||||
// empty_moov apparently writes some info at the start of the file.
|
||||
if (av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0) < 0) {
|
||||
printf("unable to set movflags opt\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_output(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (av_dict_set_int(&opts, "flush_packets", 1, 0) < 0) {
|
||||
printf("unable to set flush_packets opt\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_output(output);
|
||||
av_dict_free(&opts);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (avformat_write_header(vs->output_format_ctx, &opts) < 0) {
|
||||
if (avformat_write_header(output->format_ctx, &opts) < 0) {
|
||||
printf("unable to write header\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_output(output);
|
||||
av_dict_free(&opts);
|
||||
return NULL;
|
||||
}
|
||||
@@ -195,7 +232,7 @@ vs_open(const char * const input_format_name, const char * const input_url,
|
||||
// appropriate to set through the avformat_write_header().
|
||||
if (av_dict_count(opts) != 0) {
|
||||
printf("some options not set\n");
|
||||
vs_destroy(vs);
|
||||
vs_destroy_output(output);
|
||||
av_dict_free(&opts);
|
||||
return NULL;
|
||||
}
|
||||
@@ -203,103 +240,120 @@ vs_open(const char * const input_format_name, const char * const input_url,
|
||||
av_dict_free(&opts);
|
||||
|
||||
|
||||
return vs;
|
||||
return output;
|
||||
}
|
||||
|
||||
void
|
||||
vs_destroy(struct Videostreamer * const vs)
|
||||
vs_destroy_output(struct VSOutput * const output)
|
||||
{
|
||||
if (!vs) {
|
||||
if (!output) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vs->input_format_ctx) {
|
||||
avformat_close_input(&vs->input_format_ctx);
|
||||
avformat_free_context(vs->input_format_ctx);
|
||||
}
|
||||
|
||||
if (vs->output_format_ctx) {
|
||||
// Write file trailer.
|
||||
if (av_write_trailer(vs->output_format_ctx) != 0) {
|
||||
if (output->format_ctx) {
|
||||
if (av_write_trailer(output->format_ctx) != 0) {
|
||||
printf("unable to write trailer\n");
|
||||
}
|
||||
|
||||
if (avio_closep(&vs->output_format_ctx->pb) != 0) {
|
||||
if (avio_closep(&output->format_ctx->pb) != 0) {
|
||||
printf("avio_closep failed\n");
|
||||
}
|
||||
|
||||
avformat_free_context(vs->output_format_ctx);
|
||||
avformat_free_context(output->format_ctx);
|
||||
}
|
||||
|
||||
free(vs);
|
||||
free(output);
|
||||
}
|
||||
|
||||
// Read a compressed and encoded frame as a packet.
|
||||
//
|
||||
// Returns:
|
||||
// -1 if error
|
||||
// 0 if nothing useful read (e.g., non-video packet)
|
||||
// 1 if read a packet
|
||||
int
|
||||
vs_read_write(const struct Videostreamer * vs, const bool verbose)
|
||||
vs_read_packet(const struct VSInput * input, AVPacket * const pkt,
|
||||
const bool verbose)
|
||||
{
|
||||
if (!vs) {
|
||||
if (!input || !pkt) {
|
||||
printf("%s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(pkt, 0, sizeof(AVPacket));
|
||||
|
||||
|
||||
// Read encoded frame (as a packet).
|
||||
AVPacket pkt;
|
||||
memset(&pkt, 0, sizeof(AVPacket));
|
||||
|
||||
if (av_read_frame(vs->input_format_ctx, &pkt) != 0) {
|
||||
if (av_read_frame(input->format_ctx, pkt) != 0) {
|
||||
printf("unable to read frame\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Ignore it if it's not our video stream.
|
||||
if (pkt.stream_index != vs->video_stream_index) {
|
||||
|
||||
if (pkt->stream_index != input->video_stream_index) {
|
||||
if (verbose) {
|
||||
printf("skipping non video packet\n");
|
||||
}
|
||||
|
||||
av_packet_unref(pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
AVStream * const in_stream = vs->input_format_ctx->streams[pkt.stream_index];
|
||||
AVStream * const out_stream =
|
||||
vs->output_format_ctx->streams[pkt.stream_index];
|
||||
|
||||
if (verbose) {
|
||||
__vs_log_packet(vs->input_format_ctx, &pkt, "in");
|
||||
__vs_log_packet(input->format_ctx, pkt, "in");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Copy packet
|
||||
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,
|
||||
// We change the packet's pts, dts, duration, pos.
|
||||
//
|
||||
// We do not unref it.
|
||||
//
|
||||
// Returns:
|
||||
// -1 if error
|
||||
// 1 if wrote packet
|
||||
int
|
||||
vs_write_packet(const struct VSInput * const input,
|
||||
const struct VSOutput * const output, AVPacket * const pkt,
|
||||
const bool verbose)
|
||||
{
|
||||
if (!input || !output || !pkt) {
|
||||
printf("%s\n", strerror(EINVAL));
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVStream * const in_stream = input->format_ctx->streams[pkt->stream_index];
|
||||
AVStream * const out_stream = output->format_ctx->streams[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,
|
||||
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,
|
||||
pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base,
|
||||
out_stream->time_base);
|
||||
pkt.pos = -1;
|
||||
pkt->pos = -1;
|
||||
|
||||
|
||||
if (verbose) {
|
||||
__vs_log_packet(vs->output_format_ctx, &pkt, "out");
|
||||
__vs_log_packet(output->format_ctx, pkt, "out");
|
||||
}
|
||||
|
||||
|
||||
const int pkt_size = pkt.size;
|
||||
|
||||
// Write encoded frame (as a packet).
|
||||
|
||||
// av_interleaved_write_frame() works too, but I don't think it is needed.
|
||||
// Using av_write_frame() skips buffering.
|
||||
if (av_write_frame(vs->output_format_ctx, &pkt) != 0) {
|
||||
if (av_write_frame(output->format_ctx, pkt) != 0) {
|
||||
printf("unable to write frame\n");
|
||||
av_packet_unref(&pkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
return pkt_size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -250,27 +250,34 @@ func videoEncoder(outPipe *os.File, inputFormat, inputURL string,
|
||||
stopChan <-chan struct{}, doneChan chan<- struct{}, frameChan chan<- int) {
|
||||
inputFormatC := C.CString(inputFormat)
|
||||
inputURLC := C.CString(inputURL)
|
||||
outputFormatC := C.CString("mp4")
|
||||
outputURLC := C.CString(fmt.Sprintf("pipe:%d", outPipe.Fd()))
|
||||
verbose := C.bool(true)
|
||||
|
||||
vs := C.vs_open(inputFormatC, inputURLC, outputFormatC, outputURLC, verbose)
|
||||
if vs == nil {
|
||||
input := C.vs_open_input(inputFormatC, inputURLC, verbose)
|
||||
if input == nil {
|
||||
log.Printf("Unable to open input")
|
||||
C.free(unsafe.Pointer(inputFormatC))
|
||||
C.free(unsafe.Pointer(inputURLC))
|
||||
C.free(unsafe.Pointer(outputFormatC))
|
||||
C.free(unsafe.Pointer(outputURLC))
|
||||
doneChan <- struct{}{}
|
||||
return
|
||||
}
|
||||
C.free(unsafe.Pointer(inputFormatC))
|
||||
C.free(unsafe.Pointer(inputURLC))
|
||||
defer C.vs_destroy_input(input)
|
||||
|
||||
outputFormatC := C.CString("mp4")
|
||||
outputURLC := C.CString(fmt.Sprintf("pipe:%d", outPipe.Fd()))
|
||||
|
||||
output := C.vs_open_output(outputFormatC, outputURLC, input, verbose)
|
||||
if output == nil {
|
||||
log.Printf("Unable to open output")
|
||||
C.free(unsafe.Pointer(outputFormatC))
|
||||
C.free(unsafe.Pointer(outputURLC))
|
||||
doneChan <- struct{}{}
|
||||
return
|
||||
}
|
||||
C.free(unsafe.Pointer(outputFormatC))
|
||||
C.free(unsafe.Pointer(outputURLC))
|
||||
defer C.vs_destroy(vs)
|
||||
|
||||
frameChan <- 792
|
||||
defer C.vs_destroy_output(output)
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -282,21 +289,33 @@ func videoEncoder(outPipe *os.File, inputFormat, inputURL string,
|
||||
default:
|
||||
}
|
||||
|
||||
frameSize := C.int(0)
|
||||
frameSize = C.vs_read_write(vs, verbose)
|
||||
if frameSize == -1 {
|
||||
log.Printf("Failure decoding/encoding")
|
||||
var pkt C.AVPacket
|
||||
readRes := C.int(0)
|
||||
readRes = C.vs_read_packet(input, &pkt, verbose)
|
||||
if readRes == -1 {
|
||||
log.Printf("Failure reading")
|
||||
doneChan <- struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
// We did some work.
|
||||
|
||||
log.Printf("frame size %d", frameSize)
|
||||
|
||||
if frameSize > 0 {
|
||||
frameChan <- int(frameSize)
|
||||
if readRes == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
writeRes := C.int(0)
|
||||
writeRes = C.vs_write_packet(input, output, &pkt, verbose)
|
||||
if writeRes == -1 {
|
||||
log.Printf("Failure writing")
|
||||
C.av_packet_unref(&pkt)
|
||||
doneChan <- struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
pktSize := int(pkt.size)
|
||||
|
||||
C.av_packet_unref(&pkt)
|
||||
|
||||
frameChan <- int(pktSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,24 +4,40 @@
|
||||
#include <libavformat/avformat.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct Videostreamer {
|
||||
AVFormatContext * input_format_ctx;
|
||||
AVFormatContext * output_format_ctx;
|
||||
struct VSInput {
|
||||
AVFormatContext * format_ctx;
|
||||
int video_stream_index;
|
||||
};
|
||||
|
||||
struct VSOutput {
|
||||
AVFormatContext * format_ctx;
|
||||
};
|
||||
|
||||
void
|
||||
vs_setup(void);
|
||||
|
||||
struct Videostreamer *
|
||||
vs_open(const char * const, const char * const,
|
||||
const char * const, const char * const,
|
||||
struct VSInput *
|
||||
vs_open_input(const char * const,
|
||||
const char * const, const bool);
|
||||
|
||||
void
|
||||
vs_destroy_input(struct VSInput * const);
|
||||
|
||||
struct VSOutput *
|
||||
vs_open_output(const char * const,
|
||||
const char * const, const struct VSInput * const,
|
||||
const bool);
|
||||
|
||||
void
|
||||
vs_destroy(struct Videostreamer * const);
|
||||
vs_destroy_output(struct VSOutput * const);
|
||||
|
||||
int
|
||||
vs_read_write(const struct Videostreamer *, const bool);
|
||||
vs_read_packet(const struct VSInput *, AVPacket * const,
|
||||
const bool);
|
||||
|
||||
int
|
||||
vs_write_packet(const struct VSInput * const,
|
||||
const struct VSOutput * const, AVPacket * const,
|
||||
const bool);
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user