├── .gitignore ├── README.md ├── CMakeLists.txt ├── LICENSE.txt ├── muxer.h ├── test.cpp └── muxer.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug/ 2 | .DS_Store 3 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mediaMuxer 2 | 3 | A thread-safe muxer based on ffmpeg2.8.*.The muxer can mux h264 and aac to mp4、rtmp、flv. 4 | 5 | # usage 6 | 7 | include the muxer.h, and add videoStream or audioStream, then feed the data to the muxer. 8 | 9 | # thiry_party 10 | 11 | ffmpeg4.* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 2.8) 3 | set(CMAKE_CXX_STANDARD 11) 4 | 5 | add_executable(test test.cpp muxer.cpp muxer.h) 6 | 7 | find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h) 8 | find_library(AVCODEC_LIBRARY avcodec) 9 | 10 | find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h) 11 | find_library(AVFORMAT_LIBRARY avformat) 12 | 13 | find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h) 14 | find_library(AVUTIL_LIBRARY avutil) 15 | 16 | find_path(AVDEVICE_INCLUDE_DIR libavdevice/avdevice.h) 17 | find_library(AVDEVICE_LIBRARY avdevice) 18 | 19 | target_include_directories(test PRIVATE ${AVCODEC_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${AVDEVICE_INCLUDE_DIR}) 20 | target_link_libraries(test PRIVATE ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY} ${AVUTIL_LIBRARY} ${AVDEVICE_LIBRARY}) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /muxer.h: -------------------------------------------------------------------------------- 1 | #ifndef _MUXER_H 2 | #define _MUXER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct AVFormatContext; 10 | struct AVStream; 11 | struct AVCodecContext; 12 | struct AVCodec; 13 | struct AVIOInterruptCB; 14 | 15 | namespace jt 16 | { 17 | 18 | class Muxer; 19 | int InterruptCallBack(void*); 20 | std::string FFmpegErrorString(int code); 21 | 22 | class Muxer { 23 | public: 24 | Muxer(const std::string &format, const std::string &output_file); 25 | 26 | ~Muxer() noexcept { 27 | Close(); 28 | } 29 | 30 | /* 31 | * @param options: set context opt 32 | * 33 | */ 34 | bool Open(const std::map &options); 35 | 36 | void Interrupt() { 37 | io_interrupt_result_ = true; 38 | } 39 | 40 | bool Close(); 41 | 42 | /* 43 | * @param options: set stream dict 44 | * 45 | */ 46 | bool AddVideoStream(int width, 47 | int height, 48 | const uint8_t *video_headrer, 49 | int header_size, 50 | const std::map &options); 51 | 52 | bool AddAudioStream(const uint8_t *aac_header, 53 | int header_size, 54 | int sample_rate, 55 | int channels, 56 | int bitrate); 57 | 58 | bool SetMetaData(const char *key, const char *val); 59 | bool WriteHeader(); 60 | bool WriteH264Nalu(const uint8_t *nalu, int nalu_len, int64_t pts, int64_t dts, bool is_key); 61 | bool WriteAAC(const uint8_t *aac, int size, int64_t pts); 62 | 63 | private: 64 | friend int InterruptCallBack(void*); 65 | bool WriteVideoPacket(const uint8_t *nalu, 66 | int nalu_len, 67 | int64_t pts, 68 | int64_t dts, 69 | bool is_key); 70 | 71 | private: 72 | std::string output_format_; 73 | std::string output_file_; 74 | 75 | AVFormatContext *out_context_ = nullptr; 76 | AVStream *video_stream_ = nullptr; 77 | AVStream *audio_stream_ = nullptr; 78 | std::shared_ptr interrupt_cb_; 79 | 80 | bool is_first_video_ = false; 81 | int64_t last_video_pkt_pts_ = 0; 82 | bool open_ = false; 83 | bool io_interrupt_result_ = false; 84 | std::mutex write_mtx_; 85 | }; 86 | 87 | } 88 | 89 | #endif 90 | 91 | -------------------------------------------------------------------------------- /test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by jerett on 15/12/30. 3 | // 4 | 5 | #include 6 | 7 | extern "C" { 8 | #include 9 | }; 10 | 11 | #include 12 | #include 13 | #include "muxer.h" 14 | 15 | int main(int argc, char* argv[]) { 16 | AVFormatContext *ifmt_ctx = NULL; 17 | if (argc < 3) { 18 | fprintf(stderr, "usage: %s in_file out_file", argv[0]); 19 | return 0; 20 | } 21 | const char *in_filename = argv[1];//Input file URL 22 | const char *out_filename = argv[2];//Input file URL 23 | 24 | int ret = 0; 25 | //Input 26 | if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) { 27 | printf("Could not Open input file."); 28 | return -1; 29 | } 30 | if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) { 31 | printf("Failed to retrieve input stream information"); 32 | return -1; 33 | } 34 | 35 | jt::Muxer muxer("mp4", out_filename); 36 | 37 | std::map options; 38 | muxer.Open(options); 39 | 40 | int video_index = -1; 41 | int audio_index = -1; 42 | 43 | for (int i = 0; i < ifmt_ctx->nb_streams; i++) { 44 | //Create output AVStream according to input AVStream 45 | AVStream *in_stream = ifmt_ctx->streams[i]; 46 | 47 | if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 48 | std::map options; 49 | muxer.AddVideoStream(in_stream->codecpar->width, 50 | in_stream->codecpar->height, 51 | in_stream->codecpar->extradata, 52 | in_stream->codecpar->extradata_size, 53 | options); 54 | video_index = i; 55 | } else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 56 | audio_index = i; 57 | muxer.AddAudioStream(in_stream->codecpar->extradata, 58 | in_stream->codecpar->extradata_size, 59 | in_stream->codecpar->sample_rate, 60 | in_stream->codecpar->channels, 61 | in_stream->codecpar->bit_rate); 62 | } else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { 63 | } else { 64 | break; 65 | } 66 | } 67 | 68 | muxer.WriteHeader(); 69 | 70 | while (true) { 71 | AVPacket pkt; 72 | //Get an AVPacket 73 | if (av_read_frame(ifmt_ctx, &pkt) < 0) break; 74 | 75 | AVStream* read_stream = ifmt_ctx->streams[pkt.stream_index]; 76 | if (pkt.stream_index == video_index) { 77 | uint8_t *nalu = pkt.data; 78 | int nalu_size = pkt.size; 79 | double pts_ms = pkt.pts * av_q2d(read_stream->time_base) * 1000; 80 | double dts_ms = pkt.dts * av_q2d(read_stream->time_base) * 1000; 81 | muxer.WriteH264Nalu(nalu, nalu_size, pts_ms, dts_ms, (pkt.flags & AV_PKT_FLAG_KEY)); 82 | } else if (pkt.stream_index == audio_index) { 83 | double timestamp = pkt.pts * av_q2d(read_stream->time_base) * 1000; 84 | muxer.WriteAAC(pkt.data, pkt.size, timestamp); 85 | } 86 | av_packet_unref(&pkt); 87 | } 88 | 89 | if (ret < 0 && ret != AVERROR_EOF) { 90 | printf( "Error occurred.\n"); 91 | return -1; 92 | } 93 | return 0; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /muxer.cpp: -------------------------------------------------------------------------------- 1 | #include "muxer.h" 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | } 7 | #include 8 | 9 | 10 | namespace jt 11 | { 12 | 13 | int InterruptCallBack(void* opaque) { 14 | Muxer *mux = static_cast(opaque); 15 | return mux->io_interrupt_result_; 16 | } 17 | 18 | std::string FFmpegErrorString(int code) { 19 | char buf[256]; 20 | av_strerror(code, buf, 256); 21 | // std::cerr << buf << std::endl; 22 | return buf; 23 | } 24 | 25 | Muxer::Muxer(const std::string &format, 26 | const std::string &output_file) 27 | :output_format_(format), output_file_(output_file) { 28 | avformat_network_init(); 29 | interrupt_cb_.reset(new AVIOInterruptCB); 30 | interrupt_cb_->callback = InterruptCallBack; 31 | interrupt_cb_->opaque = this; 32 | } 33 | 34 | bool Muxer::SetMetaData(const char *key, const char *val) { 35 | av_dict_set(&out_context_->metadata, key, val, 0); 36 | return true; 37 | } 38 | 39 | bool Muxer::Open(const std::map &options) { 40 | io_interrupt_result_ = false; 41 | int ret = avformat_alloc_output_context2(&out_context_, 42 | nullptr, 43 | output_format_.data(), 44 | output_file_.data()); 45 | out_context_->oformat->flags |= AVFMT_ALLOW_FLUSH; 46 | for(const auto &option : options) { 47 | av_opt_set(out_context_->priv_data, option.first.c_str(), option.second.c_str(), 0); 48 | } 49 | return ret >= 0; 50 | } 51 | 52 | bool Muxer::AddAudioStream(const uint8_t *aac_header, 53 | int header_size, 54 | int sample_rate, 55 | int channels, 56 | int bitrate) { 57 | audio_stream_ = avformat_new_stream(out_context_, nullptr); 58 | if (audio_stream_ == nullptr) { 59 | std::cerr << "new audio stream err" << std::endl; 60 | return false; 61 | } 62 | 63 | // AVCodecContext *audioCodecContext = audio_stream_->codec; 64 | AVCodecParameters *audioCodecPar = audio_stream_->codecpar; 65 | audioCodecPar->codec_type = AVMEDIA_TYPE_AUDIO; 66 | audioCodecPar->format = AV_SAMPLE_FMT_S16; 67 | audioCodecPar->frame_size = 1024; 68 | audioCodecPar->sample_rate = sample_rate; 69 | switch (channels) { 70 | case 1: 71 | audioCodecPar->channel_layout = AV_CH_LAYOUT_MONO; 72 | break; 73 | case 2: 74 | audioCodecPar->channel_layout = AV_CH_LAYOUT_STEREO; 75 | break; 76 | default: 77 | std::cerr << "You should handle here. " << std::endl; 78 | break; 79 | } 80 | audioCodecPar->channels = channels; 81 | audioCodecPar->bit_rate = bitrate; 82 | audioCodecPar->codec_id = AV_CODEC_ID_AAC; 83 | 84 | //copy header 85 | if (aac_header != nullptr) { 86 | audioCodecPar->extradata = new uint8_t[header_size]; 87 | memcpy(audioCodecPar->extradata, aac_header, header_size); 88 | audioCodecPar->extradata_size = header_size; 89 | } else { 90 | audioCodecPar->extradata = nullptr; 91 | audioCodecPar->extradata_size = 0; 92 | } 93 | 94 | audioCodecPar->codec_tag = 0; 95 | // if (out_context_->oformat->flags & AVFMT_GLOBALHEADER) { 96 | // // audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 97 | // } 98 | return true; 99 | } 100 | 101 | bool Muxer::AddVideoStream(int width, 102 | int height, 103 | const uint8_t *video_header, 104 | int header_size, 105 | const std::map &options) { 106 | video_stream_ = avformat_new_stream(out_context_, nullptr); 107 | if (video_stream_ == nullptr) { 108 | std::cerr << "alloc video stream err" << std::endl; 109 | return false; 110 | } 111 | 112 | AVCodecParameters *vCodecPar = video_stream_->codecpar; 113 | vCodecPar->format = AV_PIX_FMT_YUV420P; 114 | vCodecPar->width = width; 115 | vCodecPar->height = height; 116 | vCodecPar->codec_type = AVMEDIA_TYPE_VIDEO; 117 | vCodecPar->codec_id = AV_CODEC_ID_H264; 118 | 119 | //copy header 120 | vCodecPar->extradata = new uint8_t[header_size]; 121 | memcpy(vCodecPar->extradata, video_header, header_size); 122 | vCodecPar->extradata_size = header_size; 123 | vCodecPar->codec_tag = 0; 124 | // if (out_context_->oformat->flags & AVFMT_GLOBALHEADER) { 125 | // vCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 126 | // } 127 | for(const auto &option : options) { 128 | av_dict_set(&video_stream_->metadata, option.first.c_str(), option.second.c_str(), 0); 129 | } 130 | return true; 131 | } 132 | 133 | bool Muxer::WriteHeader() { 134 | int ret; 135 | if (!(out_context_->oformat->flags & AVFMT_NOFILE)) { 136 | // ret = avio_open(&out_context_->pb, output_file_.data(), AVIO_FLAG_WRITE); 137 | ret = avio_open2(&out_context_->pb, output_file_.c_str(), AVIO_FLAG_WRITE, interrupt_cb_.get(), nullptr); 138 | if (ret < 0) { 139 | std::cerr << "create oformat failed:" << FFmpegErrorString(ret) << std::endl; 140 | if (!(out_context_->oformat->flags & AVFMT_NOFILE)) { 141 | avio_close(out_context_->pb); 142 | } 143 | avformat_free_context(out_context_); 144 | return false; 145 | } 146 | } 147 | 148 | ret = avformat_write_header(out_context_, nullptr); 149 | if (ret < 0) { 150 | std::cerr << "create header failed:" << FFmpegErrorString(ret) << std::endl; 151 | return false; 152 | } 153 | av_dump_format(out_context_, 0, output_file_.data(), 1); 154 | open_ = true; 155 | return true; 156 | } 157 | 158 | bool Muxer::Close() { 159 | std::lock_guard lck(write_mtx_); 160 | if (open_) { 161 | open_ = false; 162 | int ret = av_write_trailer(out_context_); 163 | if (ret != 0) { 164 | std::cerr << "write trailer failed:" << FFmpegErrorString(ret) << std::endl; 165 | // return false; 166 | } 167 | if (!(out_context_->oformat->flags & AVFMT_NOFILE)) { 168 | avio_close(out_context_->pb); 169 | } 170 | avformat_free_context(out_context_); 171 | 172 | out_context_ = 0; 173 | std::cerr << "Close " << output_file_ << std::endl; 174 | } 175 | return true; 176 | } 177 | 178 | bool Muxer::WriteAAC(const uint8_t *aac, int size, int64_t pts) { 179 | std::lock_guard lck(write_mtx_); 180 | if (!out_context_ || !open_) { 181 | std::cerr << "try write aac when not Open " << std::endl; 182 | return false; 183 | } 184 | AVPacket pkt = {0}; 185 | av_init_packet(&pkt); 186 | 187 | pkt.data = (uint8_t *) aac; 188 | pkt.size = size; 189 | 190 | static AVRational rational = {1, 1000}; 191 | pkt.pts = av_rescale_q(pts, rational, audio_stream_->time_base); 192 | // pkt.pts = pts / 1000.0 / av_q2d(audio_stream_->time_base); 193 | pkt.dts = pkt.pts; 194 | pkt.convergence_duration = AV_NOPTS_VALUE; 195 | pkt.pos = -1; 196 | 197 | pkt.stream_index = audio_stream_->index; 198 | //LOG(INFO) << "write aac"; 199 | int ret = av_interleaved_write_frame(out_context_, &pkt); 200 | if (ret != 0) { 201 | std::cerr << "write audio frame err:" << FFmpegErrorString(ret) << std::endl; 202 | } 203 | return ret == 0; 204 | } 205 | 206 | 207 | bool Muxer::WriteVideoPacket(const uint8_t *nalu, int nalu_len, int64_t pts, int64_t dts, bool is_key) { 208 | int len = nalu_len; 209 | AVPacket pkt = {0}; 210 | av_init_packet(&pkt); 211 | 212 | pkt.flags = is_key ? AV_PKT_FLAG_KEY : 0; 213 | pkt.data = (uint8_t *) nalu; 214 | pkt.size = len; 215 | pkt.stream_index = video_stream_->index; 216 | 217 | static AVRational rational = {1, 1000}; 218 | pkt.pts = av_rescale_q(pts, rational, video_stream_->time_base); 219 | pkt.dts = av_rescale_q(dts, rational, video_stream_->time_base); 220 | if (!is_first_video_) { 221 | last_video_pkt_pts_ = pkt.pts; 222 | is_first_video_ = true; 223 | } 224 | pkt.duration = static_cast(pkt.pts - last_video_pkt_pts_); 225 | last_video_pkt_pts_ = pkt.pts; 226 | // std::cout << " duration:" << pkt.duration << std::endl; 227 | pkt.convergence_duration = AV_NOPTS_VALUE; 228 | pkt.pos = -1; 229 | 230 | int ret = av_interleaved_write_frame(out_context_, &pkt); 231 | if (ret != 0) { 232 | std::cerr << "write video frame err:" << FFmpegErrorString(ret) << std::endl; 233 | } 234 | return ret == 0; 235 | } 236 | 237 | bool Muxer::WriteH264Nalu(const uint8_t *nalu, 238 | int nalu_len, 239 | int64_t pts, 240 | int64_t dts, 241 | bool is_key) { 242 | // std::cout << "is key:" << is_key << std::endl; 243 | std::lock_guard lck(write_mtx_); 244 | if (!out_context_ || !open_) { 245 | std::cerr << "try write nalu when not Open " << std::endl; 246 | return false; 247 | } 248 | 249 | if (nalu == nullptr) { 250 | av_write_frame(out_context_, nullptr); 251 | return true; 252 | } 253 | 254 | return WriteVideoPacket(nalu, nalu_len, pts, dts, is_key); 255 | } 256 | 257 | } 258 | 259 | --------------------------------------------------------------------------------