#include "fm_video_edit.h" #if (FM_VIDEO_EDIT) #if (USE_LIB_MP4V2) #include "mp4v2.h" #else #ifdef __cplusplus extern "C" { #include #include } #endif // extern C #endif //USE_LIB_MP4V2 #define MAX_VIDEO_STREAM_COUNT 10 FMVideoEdit::FMVideoEdit(QStringList files, QStringList dests, EditMode mode, bool deleteSrcDone) { _files = files; _dests = dests; _mode = mode; _deleteSrcAfter = deleteSrcDone; } void FMVideoEdit::run() { _splitMP4VideoTracks(); } int _find_video_stream_index(int stream_id, int *streams) { for(int i=0;imetadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { qInfo() << tag->key << ":" << tag->value << __FUNCTION__; } */ // 스트림 정보 읽기 if (avformat_find_stream_info(input_ctx, NULL) < 0) { emit done(ErrorOpenStream); avformat_close_input(&input_ctx); return; } // 각 비디오 스트림 + 오디오/자막 스트림 인덱스 확인 for (unsigned int i = 0; i < input_ctx->nb_streams; i++) { AVMediaType type = input_ctx->streams[i]->codecpar->codec_type; if (type == AVMEDIA_TYPE_VIDEO) { // 프레임이 없는 N 번째 영상 스트림 제거 video_stream_validation[i] = (input_ctx->streams[i]->nb_frames > 0); // if(input_ctx->streams[i]->nb_frames == 0) { // video_stream_validation[i] = false; // } if(video_stream_validation[i]) { real_video_stream_count += 1; } // Progress 처리하기 위해 if(video_frame_count < 0) { video_frame_count = input_ctx->streams[i]->nb_frames; } // AVStream* ps = input_ctx->streams[i]; // qInfo() << "VSTREAM:" << i << ps->discard << __FUNCTION__; video_stream_index[video_stream_count] = i; video_stream_count++; } else if (type == AVMEDIA_TYPE_AUDIO) { audio_stream_index = i; } //else if (type == AVMEDIA_TYPE_SUBTITLE) { // subtitle_stream_index = i; //} } // 실제 영상 스트림이 출력 개수보다 많을 경우에만 처리 //video_stream_count = 2; if(real_video_stream_count > _dests.size()) { emit done(ErrorWrongTrackCount); avformat_close_input(&input_ctx); return; } // 한번에 모든 패킷을 여러 파일에 저장하기 위해 동시 생성 AVFormatContext *output_ctx[MAX_VIDEO_STREAM_COUNT] = {NULL,}; AVStream *out_video_stream[MAX_VIDEO_STREAM_COUNT] = {NULL,}; AVStream *out_audio_stream[MAX_VIDEO_STREAM_COUNT] = {NULL,}; AVStream *out_subtitle_stream[MAX_VIDEO_STREAM_COUNT] = {NULL,}; for(int s=0;scodecpar, input_ctx->streams[video_stream_index[s]]->codecpar); if (ret < 0) { emit done(ErrorCopyStreamParameters); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } out_video_stream[s]->codecpar->codec_tag = 0; // 나머지 스트림들 복사 for (unsigned int i = 0; i < input_ctx->nb_streams; i++) { AVMediaType type = input_ctx->streams[i]->codecpar->codec_type; if (type == AVMEDIA_TYPE_AUDIO) { out_audio_stream[s] = avformat_new_stream(output_ctx[s], NULL); if (!out_audio_stream[s]) { emit done(ErrorCreateStream); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } ret = avcodec_parameters_copy(out_audio_stream[s]->codecpar, input_ctx->streams[i]->codecpar); if (ret < 0) { emit done(ErrorCopyStreamParameters); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } out_audio_stream[s]->codecpar->codec_tag = 0; } // 자막 스트림 복사 if (type == AVMEDIA_TYPE_SUBTITLE) { out_subtitle_stream[s] = avformat_new_stream(output_ctx[s], NULL); if (!out_subtitle_stream[s]) { emit done(ErrorCreateStream); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } ret = avcodec_parameters_copy(out_subtitle_stream[s]->codecpar, input_ctx->streams[i]->codecpar); if (ret < 0) { emit done(ErrorCopyStreamParameters); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } out_subtitle_stream[s]->codecpar->codec_tag = 0; } } // 나머지 스트림 복사 // 출력 파일 헤더 쓰기 av_dump_format(output_ctx[s], 0, dest, 1); if (!(output_ctx[s]->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&output_ctx[s]->pb, dest, AVIO_FLAG_WRITE) < 0) { emit done(ErrorFileWrite); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); return; } } // major_brand를 'isom'으로 설정 // AVDictionary *options = NULL; // av_dict_set(&options, "major_brand", "isom", 0); // av_dict_set(&options, "compatible_brands", "isomiso2avc1mp41", 0); //&options int res = avformat_write_header(output_ctx[s], NULL); // &options if (res < 0) { //char ebuffer[1024] = {0,}; //av_strerror(res,ebuffer,1024); qInfo() << "VIDEO STREAM:" << s << " HEADER WRITE ERROR" << __FUNCTION__; emit done(ErrorFileWrite); _clean_output_contexts(output_ctx); avformat_close_input(&input_ctx); // av_dict_free(&options); return; } } // 파일 생성 및 스트림 정보 복사 // 패킷 복사 AVPacket packet; // for 문으로 처리시 처음으로 다시 돌아가는 기능 필요... // av_seek_frame(input_ctx, -1, 0, AVSEEK_FLAG_BACKWARD); int frame_processed = 0; // Progress 처리 while (av_read_frame(input_ctx, &packet) >= 0) { AVMediaType type = input_ctx->streams[packet.stream_index]->codecpar->codec_type; // 입력 스트림과 출력 스트림의 타임베이스 정보를 가져옵니다. const AVRational& in_time_base = input_ctx->streams[packet.stream_index]->time_base; if(type == AVMEDIA_TYPE_VIDEO) { // video 순서 index 확인 int vindex = _find_video_stream_index(packet.stream_index,video_stream_index); if(!video_stream_validation[vindex]) { continue; } // 시간계산 비디오 스트림 인덱스는 무조건 0 const AVRational& out_time_base = output_ctx[vindex]->streams[0]->time_base; // 모든 패킷 RESCALE 하지 않으면 재생시간 짧아짐 packet.pts = av_rescale_q(packet.pts, in_time_base, out_time_base); packet.dts = av_rescale_q(packet.dts, in_time_base, out_time_base); packet.duration = av_rescale_q(packet.duration, in_time_base, out_time_base); packet.stream_index = 0; // 출력 비디오 스트림 인덱스 av_interleaved_write_frame(output_ctx[vindex], &packet); if(vindex == 0 && video_frame_count > 0) { frame_processed++; int percent = int (((double)frame_processed) / ((double)video_frame_count) * 100.0); emit progress(percent); //qInfo() << progress << __FUNCTION__; } } else if (type == AVMEDIA_TYPE_AUDIO) { // 패킷을 여러번 쓰기 위해서는 av_packet_clone 해서 처리해야함.. for(int s=0;sstreams[1]->time_base; AVPacket* cp = av_packet_clone(&packet); // 모든 패킷 RESCALE 하지 않으면 재생시간 짧아짐 cp->pts = av_rescale_q(cp->pts, in_time_base, out_time_base); cp->dts = av_rescale_q(cp->dts, in_time_base, out_time_base); cp->duration = av_rescale_q(cp->duration, in_time_base, out_time_base); cp->stream_index = 1; //출력 오디오 스트림 인덱스 av_interleaved_write_frame(output_ctx[s], cp); av_packet_free(&cp); } } else if (type == AVMEDIA_TYPE_SUBTITLE) { // AUDIO 가 존재하지 않을경우 1 존재할 경우 2 const int subtitle_stream_index = audio_stream_index >= 0 ? 2 : 1; for(int s=0;sstreams[subtitle_stream_index]->time_base; AVPacket* cp = av_packet_clone(&packet); // 모든 패킷 RESCALE 하지 않으면 재생시간 짧아짐 for 에서 누적되니 신규 packet 에만 처리 cp->pts = av_rescale_q(cp->pts, in_time_base, out_time_base); cp->dts = av_rescale_q(cp->dts, in_time_base, out_time_base); cp->duration = av_rescale_q(cp->duration, in_time_base, out_time_base); cp->stream_index = subtitle_stream_index; av_interleaved_write_frame(output_ctx[s], cp); av_packet_free(&cp); } } av_packet_unref(&packet); } for(int s=0;soformat->flags & AVFMT_NOFILE)) { avio_closep(&output_ctx[s]->pb); } //av_dict_free(&options); avformat_free_context(output_ctx[s]); } avformat_close_input(&input_ctx); _afterProcess(); emit done(ErrorNone); } void FMVideoEdit::_afterProcess() { if(_deleteSrcAfter) { for(int i=0;i<_files.size();i++) { QFile(_files.at(i)).remove(); qInfo() << "DELETE:" << _files.at(i) << __FUNCTION__; } } } #if (USE_LIB_MP4V2) void FMVideoEdit::_splitVideoTracks() { QString src = _files.first(); /* 최적화 하여 마지막 이상한 atom 제거해도 수정이 안됨 QFileInfo fi = QFileInfo(src2); QString src = QDir::cleanPath(fi.dir().path() + QDir::separator() + fi.baseName() + "_2.mp4"); if(!MP4Optimize(src2.toLocal8Bit().data(),src.toLocal8Bit().data())) { //qInfo() << "OPTIMIZE FAILED.." << __FUNCTION__; return; } */ MP4FileHandle inputFile = MP4Read(src.toLocal8Bit().data()); if(MP4_INVALID_FILE_HANDLE == inputFile) { emit done(ErrorFileOpen); return; } uint32_t numTrack = MP4GetNumberOfTracks(inputFile,NULL,0); //qInfo() << QString(MP4Info(mp4File)) << __FUNCTION__; qInfo() << "numTrack" << numTrack << __FUNCTION__; QList videoTracks = QList(); QList otherTracks = QList(); // 영상을 제외한 트랙들 for(uint32_t i=0;i cloneTracks = QList(); cloneTracks.append(videoTracks.at(i)); // 영상 트랙 cloneTracks.append(otherTracks); // 나머지 트랙(자막,메타,음성) qInfo() << "cloneTracks:" << cloneTracks << __FUNCTION__; // 트랙 복제 for (int j=0;j