Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ============================================================================
- Name : ffmpegTest.c
- Author :
- Version :
- Copyright : Your copyright notice
- Description : Hello World in C, Ansi-style
- ============================================================================
- */
- #include <libavcodec/avcodec.h>
- #include <libavformat/avformat.h>
- #include <libavdevice/avdevice.h>
- #include <libavutil/timestamp.h>
- #include <inttypes.h>
- #include "stdbool.h"
- #define URL "CAMERARTSPLINK"
- int BufferSize = 512;
- int MaxTransferErrors = 100;
- AVFormatContext* mInputFormatCtx;
- AVFormatContext* mOutputFormatCtx;
- int mInputVideoStreamId;
- int64_t mOutputLastDTS;
- char* mInputFormat = "rtsp";
- char* mInputStreamUrl = URL;
- uint8_t* mOutputBuffer;
- int openInput();
- void closeInput();
- int openOutput();
- int writeOutput(void *opaque, uint8_t *buffer, int buffer_size);
- void closeOutput();
- int main() {
- mInputFormatCtx = mOutputFormatCtx = 0;
- av_register_all();
- // Make all formats available.
- avdevice_register_all();
- // Initialize network functions
- avformat_network_init();
- // Opening input stream
- if (0 > openInput()) {
- return 0;
- }
- if (0 > openOutput()) {
- return 0;
- }
- long long i = 0;
- AVPacket pkt;
- int result;
- bool requestShutdown = false;
- bool fix_dts;
- while (!requestShutdown) {
- memset(&pkt, 0, sizeof(AVPacket));
- // Read encoded frame (as a packet).
- if (av_read_frame(mInputFormatCtx, &pkt) != 0) {
- fprintf(stderr, "unable to read frame");
- closeInput();
- closeOutput();
- av_packet_unref(&pkt);
- break;
- }
- // Ignore it if it's not our video stream.
- if (pkt.stream_index != mInputVideoStreamId) {
- av_packet_unref(&pkt);
- continue;
- }
- AVStream* const inStream = mInputFormatCtx->streams[pkt.stream_index];
- if (!inStream) {
- fprintf(stderr, "input stream not found with stream index %d", pkt.stream_index);
- closeInput();
- closeOutput();
- av_packet_unref(&pkt);
- break;
- }
- // If there are multiple input streams, then the stream index on the packet
- // may not match the stream index in our output. We need to ensure the index
- // matches. Note by this point we have checked that it is indeed a packet
- // from the stream we want (we do this when reading the packet).
- //
- // As we only ever have a single output stream (one, video), the index will
- // be 0.
- if (0 != pkt.stream_index) {
- pkt.stream_index = 0;
- }
- AVStream* const outStream = mOutputFormatCtx->streams[pkt.stream_index];
- if (!outStream) {
- fprintf(stderr, "output stream not found with stream index %d", pkt.stream_index);
- closeInput();
- closeOutput();
- av_packet_unref(&pkt);
- break;
- }
- // It is possible that the input is not well formed. Its dts (decompression
- // timestamp) may fluctuate. av_write_frame() says that the dts must be
- // strictly increasing.
- //
- // Packets from such inputs might look like:
- //
- // in: pts:18750 pts_time:0.208333 dts:18750 dts_time:0.208333 duration:3750 duration_time:0.0416667 stream_index:1
- // in: pts:0 pts_time:0 dts:0 dts_time:0 duration:3750 duration_time:0.0416667 stream_index:1
- //
- // dts here is 18750 and then 0.
- //
- // If we try to write the second packet as is, we'll see this error:
- // [mp4 @ 0x10f1ae0] Application provided invalid, non monotonically increasing dts to muxer in stream 1: 18750 >= 0
- //
- // This is apparently a fairly common problem. In ffmpeg.c (as of ffmpeg
- // 3.2.4 at least) there is logic to rewrite the dts and warn if it happens.
- // Let's do the same. Note my logic is a little different here.
- fix_dts = pkt.dts != AV_NOPTS_VALUE &&
- mOutputLastDTS != AV_NOPTS_VALUE &&
- pkt.dts <= mOutputLastDTS;
- // It is also possible for input streams to include a packet with
- // dts/pts=NOPTS after packets with dts/pts set. These won't be caught by the
- // prior case. If we try to send these to the encoder however, we'll generate
- // the same error (non monotonically increasing DTS) since the output packet
- // will have dts/pts=0.
- fix_dts |= pkt.dts == AV_NOPTS_VALUE && mOutputLastDTS != AV_NOPTS_VALUE;
- if (fix_dts) {
- int64_t const next_dts = mOutputLastDTS + 1;
- fprintf(stderr, "Warning: Non-monotonous DTS in input stream. \n"
- "Previous: %ld\n"
- "current: %ld\n"
- "Changing to %ld.\n", mOutputLastDTS, pkt.dts, next_dts);
- // We also apparently (ffmpeg.c does this too) need to update the pts.
- // Otherwise we see an error like:
- //
- // [mp4 @ 0x555e6825ea60] pts (3780) < dts (22531) in stream 0
- if (pkt.pts != AV_NOPTS_VALUE && pkt.pts >= pkt.dts) {
- pkt.pts = FFMAX(pkt.pts, next_dts);
- }
- // In the case where pkt->dts was AV_NOPTS_VALUE, pkt->pts can be
- // AV_NOPTS_VALUE too which we fix as well.
- if (pkt.pts == AV_NOPTS_VALUE) {
- pkt.pts = next_dts;
- }
- pkt.dts = next_dts;
- }
- // Set pts/dts if not set. Otherwise we will receive warnings like
- //
- // [mp4 @ 0x55688397bc40] Timestamps are unset in a packet for stream 0. This
- // is deprecated and will stop working in the future. Fix your code to set
- // the timestamps properly
- //
- // [mp4 @ 0x55688397bc40] Encoder did not produce proper pts, making some up.
- if (pkt.pts == AV_NOPTS_VALUE) {
- pkt.pts = 0;
- } else {
- pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base,
- outStream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
- }
- if (pkt.dts == AV_NOPTS_VALUE) {
- pkt.dts = 0;
- } else {
- pkt.dts = av_rescale_q_rnd(pkt.dts, inStream->time_base,
- outStream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
- }
- pkt.duration = av_rescale_q(pkt.duration, inStream->time_base,
- outStream->time_base);
- pkt.pos = -1;
- // Track last dts we see (see where we use it for why).
- mOutputLastDTS = pkt.dts;
- // 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.
- result = av_write_frame(mOutputFormatCtx, &pkt);
- if (0 != result) {
- fprintf(stderr, "unable to write frame.");
- fprintf(stderr, "%s", av_err2str(result));
- closeInput();
- closeOutput();
- av_packet_unref(&pkt);
- break;
- }
- av_packet_unref(&pkt);
- i++;
- if (i == 1000) {
- requestShutdown = true;
- }
- }
- closeInput();
- closeOutput();
- }
- int openInput() {
- AVInputFormat* const inputFormat = av_find_input_format(mInputFormat);
- if (!inputFormat) {
- fprintf(stderr, "input format not found");
- return -2;
- }
- AVDictionary* opts = NULL;
- int const openStatus = avformat_open_input(&mInputFormatCtx, mInputStreamUrl, inputFormat, &opts);
- if (0 != openStatus) {
- fprintf(stderr, "unable to open input.");
- fprintf(stderr, "%s", av_err2str(openStatus));
- closeInput();
- return -4;
- }
- if (0 > avformat_find_stream_info(mInputFormatCtx, &opts)) {
- fprintf(stderr, "failed to find stream info");
- closeInput();
- return -5;
- }
- av_dict_free(&opts);
- // Find the first video stream.
- mInputVideoStreamId = -1;
- for (unsigned int i = 0; i < mInputFormatCtx->nb_streams; i++) {
- AVStream* const inStream = mInputFormatCtx->streams[i];
- if (inStream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
- continue;
- }
- mInputVideoStreamId = (int)i;
- break;
- }
- if (-1 == mInputVideoStreamId) {
- fprintf(stderr, "no video stream found");
- closeInput();
- return -6;
- }
- return 0;
- }
- void closeInput() {
- if (mInputFormatCtx) {
- avformat_close_input(&mInputFormatCtx);
- }
- }
- int openOutput() {
- AVOutputFormat* const outputFormat = av_guess_format("mp4", NULL, NULL);
- if (!outputFormat) {
- fprintf(stderr, "output format not found");
- closeOutput();
- return -1;
- }
- if (0 != mOutputBuffer) {
- fprintf(stderr, "output buffer is not empty!");
- closeOutput();
- return -2;
- }
- mOutputBuffer = (uint8_t *)av_malloc(BufferSize);
- int const allocStatus = avformat_alloc_output_context2(&mOutputFormatCtx, outputFormat, NULL, NULL);
- if (0 != allocStatus) {
- fprintf(stderr, "unable to allocate output context.");
- fprintf(stderr, "%s", av_err2str(allocStatus));
- closeOutput();
- return -3;
- }
- mOutputFormatCtx->pb = avio_alloc_context(
- mOutputBuffer, BufferSize, // internal buffer and its size
- 1, // write flag (1=true, 0=false)
- 0, // user data, will be passed to our cb
- 0, // no read
- &writeOutput, // write callback function
- 0 // no seek
- );
- mOutputFormatCtx->flags |= AVFMT_FLAG_CUSTOM_IO;
- mOutputFormatCtx->oformat = outputFormat;
- // Copy the input video stream.
- AVStream* const outStream = avformat_new_stream(mOutputFormatCtx, NULL);
- if (!outStream) {
- fprintf(stderr, "unable to add stream");
- closeOutput();
- return -4;
- }
- AVStream* const inStream = mInputFormatCtx->streams[mInputVideoStreamId];
- if (0 > avcodec_parameters_copy(outStream->codecpar, inStream->codecpar)) {
- fprintf(stderr, "unable to copy codec parameters");
- closeOutput();
- return -5;
- }
- // Write file header.
- AVDictionary* opts = NULL;
- // -movflags frag_keyframe tells the mp4 muxer to fragment at each video
- // keyframe. This is necessary for it to support output to a non-seekable
- // file (e.g., pipe).
- //
- // -movflags isml+frag_keyframe is the same, except isml appears to be to
- // make the output a live smooth streaming feed (as opposed to not live). I'm
- // not sure the difference, but isml appears to be a microsoft
- // format/protocol.
- //
- // To specify both, use isml+frag_keyframe as the value.
- //
- // I found that while Chrome had no trouble displaying the resulting mp4 with
- // just frag_keyframe, Firefox would not until I also added empty_moov.
- // empty_moov apparently writes some info at the start of the file.
- if (0 > av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0)) {
- fprintf(stderr, "unable to set movflags opt");
- closeOutput();
- return -6;
- }
- if (av_dict_set_int(&opts, "flush_packets", 1, 0) < 0) {
- fprintf(stderr, "unable to set flush_packets opt");
- closeOutput();
- av_dict_free(&opts);
- return -7;
- }
- if (0 > avformat_write_header(mOutputFormatCtx, &opts)) {
- fprintf(stderr, "unable to write header");
- closeOutput();
- av_dict_free(&opts);
- return -8;
- }
- // Check any options that were not set. Because I'm not sure if all are
- // appropriate to set through the avformat_write_header().
- if (0 != av_dict_count(opts)) {
- fprintf(stderr, "some options not set");
- closeOutput();
- av_dict_free(&opts);
- return -9;
- }
- av_dict_free(&opts);
- mOutputLastDTS = AV_NOPTS_VALUE;
- return 0;
- }
- int writeOutput(void *opaque, uint8_t *buffer, int buffer_size) {
- printf("writeOutput: writing %d bytes: ", buffer_size);
- for(int i=0;i<buffer_size;i++) {
- printf("%d, ", (int)(*(buffer+i)));
- }
- printf("\n");
- // Always return the buffer size.
- return buffer_size;
- }
- void closeOutput() {
- if (mOutputFormatCtx) {
- if (0 != av_write_trailer(mOutputFormatCtx)) {
- fprintf(stderr, "unable to write trailer");
- }
- av_freep(&mOutputBuffer);
- av_freep(&mOutputFormatCtx->pb);
- avformat_free_context(mOutputFormatCtx);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement